@yxhl/specter-pui-vtk 1.0.96 → 1.0.97

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yxhl/specter-pui-vtk",
3
- "version": "1.0.96",
3
+ "version": "1.0.97",
4
4
  "description": "雅心互联 Vue 3 + Vuetify3 组件库",
5
5
  "type": "module",
6
6
  "main": "./dist/specter-pui.umd.js",
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="panel-header" @keydown.esc.stop="closePicker">
2
+ <div ref="headerRef" class="panel-header" @keydown.esc.stop="closePicker">
3
3
  <button type="button" class="panel-nav" aria-label="上一周期" @click="handleNavigate('prev')">‹</button>
4
4
 
5
5
  <div class="panel-title">
@@ -50,7 +50,7 @@
50
50
  </template>
51
51
 
52
52
  <script setup>
53
- import { computed, ref, watch } from 'vue';
53
+ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
54
54
 
55
55
  defineOptions({
56
56
  name: 'VtkDatePanelHeader',
@@ -89,6 +89,7 @@ const getYearPageStart = (year) => year - YEAR_PAGE_CENTER_OFFSET;
89
89
 
90
90
  const openPicker = ref(null);
91
91
  const yearPageStart = ref(getYearPageStart(props.year));
92
+ const headerRef = ref(null);
92
93
 
93
94
  const yearPage = computed(() =>
94
95
  Array.from({ length: YEAR_PAGE_SIZE }, (_, index) => yearPageStart.value + index)
@@ -153,11 +154,20 @@ const handleNavigate = (direction) => {
153
154
  emit(direction);
154
155
  };
155
156
 
157
+ const handleOutsideClick = (event) => {
158
+ if (openPicker.value && !headerRef.value?.contains(event.target)) {
159
+ closePicker();
160
+ }
161
+ };
162
+
156
163
  watch(() => props.year, (year) => {
157
164
  if (openPicker.value !== 'year') {
158
165
  yearPageStart.value = getYearPageStart(year);
159
166
  }
160
167
  });
168
+
169
+ onMounted(() => document.addEventListener('click', handleOutsideClick));
170
+ onBeforeUnmount(() => document.removeEventListener('click', handleOutsideClick));
161
171
  </script>
162
172
 
163
173
  <style scoped>
@@ -1076,55 +1076,76 @@ const changeViewMode = (mode) => {
1076
1076
  viewMode.value = mode;
1077
1077
  };
1078
1078
 
1079
- // 面板操作
1080
- const initializePicker = () => {
1081
- if (props.displayMode === 'year' && props.mode === 'single') {
1082
- currentYear.value = todayYear;
1083
- }
1084
-
1085
- tempValue.value = selectedValue.value ?
1086
- (Array.isArray(selectedValue.value) ? [...selectedValue.value] : selectedValue.value) :
1087
- (props.mode === 'range' ? [] : null);
1088
- viewMode.value = props.displayMode;
1089
-
1090
- if (props.displayMode === 'month') {
1091
- rightYear.value = currentYear.value + 1;
1092
- } else {
1093
- if (currentMonth.value === 11) {
1094
- rightYear.value = currentYear.value + 1;
1095
- rightMonth.value = 0;
1096
- } else {
1097
- rightYear.value = currentYear.value;
1098
- rightMonth.value = currentMonth.value + 1;
1099
- }
1100
- }
1101
- };
1102
-
1103
- watch(showPicker, (isOpen) => {
1079
+ // 面板操作
1080
+ const parseCalendarValue = (value) => {
1081
+ if (typeof value !== 'string') return null;
1082
+
1083
+ const match = value.match(/^(\d{4})(?:-(\d{2}))?/);
1084
+ if (!match) return null;
1085
+
1086
+ const month = match[2] ? Number(match[2]) - 1 : null;
1087
+ return {
1088
+ year: Number(match[1]),
1089
+ month: month !== null && month >= 0 && month <= 11 ? month : null
1090
+ };
1091
+ };
1092
+
1093
+ const setRightPanelToNextMonth = () => {
1094
+ if (currentMonth.value === 11) {
1095
+ rightYear.value = currentYear.value + 1;
1096
+ rightMonth.value = 0;
1097
+ } else {
1098
+ rightYear.value = currentYear.value;
1099
+ rightMonth.value = currentMonth.value + 1;
1100
+ }
1101
+ };
1102
+
1103
+ const syncCalendarPanels = () => {
1104
+ const values = Array.isArray(selectedValue.value) ? selectedValue.value : [selectedValue.value];
1105
+ const start = parseCalendarValue(values[0]);
1106
+ const end = parseCalendarValue(values[1]);
1107
+
1108
+ if (start) {
1109
+ currentYear.value = start.year;
1110
+ if (start.month !== null) currentMonth.value = start.month;
1111
+ }
1112
+
1113
+ if (props.displayMode === 'year') return;
1114
+
1115
+ if (props.displayMode === 'month') {
1116
+ rightYear.value = end && end.year !== currentYear.value ? end.year : currentYear.value + 1;
1117
+ return;
1118
+ }
1119
+
1120
+ const endUsesDifferentMonth = end && end.month !== null &&
1121
+ (end.year !== currentYear.value || end.month !== currentMonth.value);
1122
+
1123
+ if (props.mode === 'range' && endUsesDifferentMonth) {
1124
+ rightYear.value = end.year;
1125
+ rightMonth.value = end.month;
1126
+ } else {
1127
+ setRightPanelToNextMonth();
1128
+ }
1129
+ };
1130
+
1131
+ const initializePicker = () => {
1132
+ tempValue.value = selectedValue.value ?
1133
+ (Array.isArray(selectedValue.value) ? [...selectedValue.value] : selectedValue.value) :
1134
+ (props.mode === 'range' ? [] : null);
1135
+ viewMode.value = props.displayMode;
1136
+ syncCalendarPanels();
1137
+ };
1138
+
1139
+ watch(showPicker, (isOpen) => {
1104
1140
  if (isOpen) {
1105
- initializePicker();
1106
- }
1107
- });
1108
- if (props.inline) {
1109
- // 内联模式下直接初始化
1110
- tempValue.value = selectedValue.value ?
1111
- (Array.isArray(selectedValue.value) ? [...selectedValue.value] : selectedValue.value) :
1112
- (props.mode === 'range' ? [] : null);
1113
- viewMode.value = props.displayMode;
1114
-
1115
- // 初始化右侧日历/年份
1116
- if (props.displayMode === 'month') {
1117
- rightYear.value = currentYear.value + 1;
1118
- } else {
1119
- if (currentMonth.value === 11) {
1120
- rightYear.value = currentYear.value + 1;
1121
- rightMonth.value = 0;
1122
- } else {
1123
- rightYear.value = currentYear.value;
1124
- rightMonth.value = currentMonth.value + 1;
1125
- }
1126
- }
1127
- }
1141
+ initializePicker();
1142
+ }
1143
+ });
1144
+
1145
+ watch(() => props.modelValue, (newValue) => {
1146
+ selectedValue.value = newValue;
1147
+ if (props.inline) initializePicker();
1148
+ }, { immediate: true });
1128
1149
 
1129
1150
  const closePicker = () => {
1130
1151
  showPicker.value = false;
@@ -1162,11 +1183,7 @@ const handleConfirm = () => {
1162
1183
  }
1163
1184
  };
1164
1185
 
1165
- // 监听外部值变化
1166
- watch(() => props.modelValue, (newValue) => {
1167
- selectedValue.value = newValue;
1168
- }, { immediate: true });
1169
- </script>
1186
+ </script>
1170
1187
 
1171
1188
  <style scoped>
1172
1189
  .vtk-date-selector {
@@ -1301,42 +1318,8 @@ watch(() => props.modelValue, (newValue) => {
1301
1318
  max-width: 340px;
1302
1319
  }
1303
1320
 
1304
- .calendar-header {
1305
- display: flex;
1306
- align-items: center;
1307
- justify-content: space-between;
1308
- padding: 4px 0;
1309
- margin-bottom: 4px;
1310
- }
1311
-
1312
- .month-title {
1313
- font-size: 12px;
1314
- font-weight: 500;
1315
- color: rgb(var(--v-theme-on-surface));
1316
- }
1317
-
1318
- .nav-btn-small {
1319
- border: none;
1320
- background: none;
1321
- font-size: 14px;
1322
- color: rgb(var(--v-theme-on-surface), 0.6);
1323
- cursor: pointer;
1324
- padding: 2px 4px;
1325
- border-radius: 4px;
1326
- transition: background 0.2s;
1327
- }
1328
-
1329
- .nav-btn-small:hover {
1330
- background: rgb(var(--v-theme-on-surface), 0.08);
1331
- }
1332
-
1333
- .nav-btn-small.invisible {
1334
- visibility: hidden;
1335
- pointer-events: none;
1336
- }
1337
-
1338
- .year-grid,
1339
- .month-grid {
1321
+ .year-grid,
1322
+ .month-grid {
1340
1323
  display: grid;
1341
1324
  grid-template-columns: repeat(3, 1fr);
1342
1325
  gap: 6px;