消毒机设备
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

323 lines
6.9 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <script lang="ts" setup>
  2. import { defineEmits, defineProps, onBeforeMount, onMounted, ref, toRefs, watchEffect } from 'vue'
  3. const props = defineProps({
  4. optionsLeft: {
  5. type: Array as () => System.Option[],
  6. required: true,
  7. },
  8. options: {
  9. type: Array as () => System.Option[],
  10. required: true,
  11. },
  12. selectedValue: {
  13. type: [String, Number, Boolean, Object],
  14. default: null,
  15. },
  16. placeholder: {
  17. type: String,
  18. default: '请选择',
  19. },
  20. searchable: {
  21. type: Boolean,
  22. default: true,
  23. },
  24. defaultValue: {
  25. type: Number,
  26. default: 0,
  27. },
  28. })
  29. const emits = defineEmits(['confirm', 'cancel'])
  30. const optionsList = ref<HTMLUListElement | null>(null)
  31. const { optionsLeft, options } = toRefs(props)
  32. const tempSelectedLeftValue = ref('positivePressure')
  33. const tempSelectedRightValue = ref(props.defaultValue)
  34. const tempSelectedValue = ref<string[]>(['positivePressure', '10%'])
  35. const filteredOptionsLeft = ref<System.Option[]>([])
  36. const filteredOptionsRight = ref<System.Option[]>([])
  37. onBeforeMount(() => {
  38. filteredOptionsLeft.value = optionsLeft.value.filter(item => item.value)
  39. filteredOptionsRight.value = options.value.filter(item => item.value)
  40. })
  41. onMounted(() => {
  42. scrollToSelectedItem()
  43. })
  44. const scrollToSelectedItem = () => {
  45. if (!optionsList.value) {
  46. return
  47. }
  48. // 获取选中的li元素(带selected类的)
  49. const selectedLi = optionsList.value.querySelector('li.selected')
  50. if (selectedLi) {
  51. const containerRect = optionsList.value.getBoundingClientRect()
  52. const itemRect = selectedLi.getBoundingClientRect()
  53. const offsetTop = itemRect.top - containerRect.top
  54. const scrollTop = offsetTop - (containerRect.height - itemRect.height) / 2
  55. optionsList.value.scrollTop = scrollTop + 10
  56. }
  57. }
  58. const selectOptionLeft = (option: System.Option) => {
  59. if (option.value === 'constantPressure') {
  60. tempSelectedValue.value = [option.value]
  61. filteredOptionsRight.value = []
  62. }
  63. else {
  64. tempSelectedValue.value[0] = option.value
  65. filteredOptionsRight.value = options.value.filter(item => item.value)
  66. }
  67. tempSelectedLeftValue.value = option.value
  68. }
  69. const selectOption = (option: System.Option) => {
  70. tempSelectedRightValue.value = option.value
  71. tempSelectedValue.value[1] = option.value
  72. }
  73. const confirmSelection = () => {
  74. emits('confirm', tempSelectedValue.value)
  75. }
  76. const handleCancel = () => {
  77. emits('cancel')
  78. }
  79. watchEffect(() => {
  80. tempSelectedRightValue.value = props.defaultValue
  81. })
  82. </script>
  83. <template>
  84. <div class="modal-overlay" @click.self="handleCancel">
  85. <div class="modal-container">
  86. <div>
  87. <div class="modal-header">
  88. <h3>{{ placeholder }}</h3>
  89. <button class="close-btn" @click="handleCancel">
  90. <i class="fa fa-times"></i>
  91. </button>
  92. </div>
  93. <div class="modal-main">
  94. <div class="modal-content">
  95. <ul class="options-list">
  96. <li
  97. v-for="(option, index) in filteredOptionsLeft"
  98. :key="option.value || index"
  99. :class="{ selected: option.value === tempSelectedLeftValue }"
  100. @click="selectOptionLeft(option)"
  101. >
  102. {{ option.label }}
  103. </li>
  104. </ul>
  105. <div v-if="!filteredOptionsLeft.length" class="no-results">
  106. 没有找到匹配项
  107. </div>
  108. </div>
  109. <div ref="optionsList" class="modal-content-right">
  110. <ul class="options-list">
  111. <li
  112. v-for="(option, index) in filteredOptionsRight"
  113. :key="option.value || index"
  114. :class="{ selected: option.value === tempSelectedRightValue }"
  115. @click="selectOption(option)"
  116. >
  117. {{ option.label }}
  118. </li>
  119. </ul>
  120. <div v-if="!filteredOptionsRight.length" class="no-results">
  121. 没有找到匹配项
  122. </div>
  123. </div>
  124. </div>
  125. </div>
  126. <div class="modal-footer">
  127. <button class="cancel-btn" @click="handleCancel">取消</button>
  128. <button class="confirm-btn" @click="confirmSelection">确定</button>
  129. </div>
  130. </div>
  131. </div>
  132. </template>
  133. <style scoped>
  134. .modal-overlay {
  135. position: fixed;
  136. top: 0;
  137. left: 0;
  138. right: 0;
  139. bottom: 0;
  140. background-color: rgba(0, 0, 0, 0.5);
  141. display: flex;
  142. align-items: center;
  143. justify-content: center;
  144. z-index: 200;
  145. }
  146. .modal-container {
  147. background-color: white;
  148. border-radius: 12px;
  149. width: 90%;
  150. max-width: 400px;
  151. max-height: 80vh;
  152. display: flex;
  153. flex-direction: column;
  154. overflow: hidden;
  155. box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  156. animation: fadeIn 0.2s ease-out;
  157. }
  158. .modal-header {
  159. padding: 16px 20px;
  160. border-bottom: 1px solid #e2e8f0;
  161. display: flex;
  162. justify-content: space-between;
  163. align-items: center;
  164. }
  165. .modal-header h3 {
  166. margin: 0;
  167. font-size: 18px;
  168. font-weight: 500;
  169. color: #1e293b;
  170. }
  171. .close-btn {
  172. background: none;
  173. border: none;
  174. cursor: pointer;
  175. font-size: 18px;
  176. color: #94a3b8;
  177. transition: color 0.2s ease;
  178. }
  179. .close-btn:hover {
  180. color: #64748b;
  181. }
  182. .modal-content {
  183. flex: 1;
  184. overflow-y: auto;
  185. padding: 10px 0;
  186. max-height: 15vw;
  187. }
  188. .modal-content-right{
  189. flex: 1;
  190. overflow-y: auto;
  191. padding: 10px 0;
  192. max-height: 15vw;
  193. }
  194. .search-box {
  195. position: relative;
  196. padding: 8px 16px;
  197. }
  198. .search-box input {
  199. width: 100%;
  200. padding: 10px 32px 10px 12px;
  201. border: 1px solid #e2e8f0;
  202. border-radius: 8px;
  203. font-size: 14px;
  204. outline: none;
  205. transition: border-color 0.2s ease;
  206. }
  207. .search-box input:focus {
  208. border-color: #3b82f6;
  209. }
  210. .search-box i {
  211. position: absolute;
  212. right: 28px;
  213. top: 50%;
  214. transform: translateY(-50%);
  215. color: #94a3b8;
  216. }
  217. .options-list {
  218. list-style: none;
  219. margin: 0;
  220. padding: 0;
  221. }
  222. .options-list li {
  223. padding: 14px 20px;
  224. font-size: 16px;
  225. color: #334155;
  226. cursor: pointer;
  227. transition: background-color 0.2s ease;
  228. text-align: center;
  229. }
  230. .options-list li:hover {
  231. background-color: #f1f5f9;
  232. }
  233. .options-list li.selected {
  234. background-color: #a4c4f1;
  235. color: #0284c7;
  236. font-weight: 500;
  237. text-align: center;
  238. }
  239. .no-results {
  240. padding: 16px 20px;
  241. font-size: 14px;
  242. color: #94a3b8;
  243. text-align: center;
  244. }
  245. .modal-footer {
  246. padding: 12px 16px;
  247. border-top: 1px solid #e2e8f0;
  248. display: flex;
  249. gap: 12px;
  250. }
  251. .modal-footer button {
  252. flex: 1;
  253. padding: 10px 16px;
  254. border-radius: 8px;
  255. font-size: 16px;
  256. cursor: pointer;
  257. transition: all 0.2s ease;
  258. }
  259. .cancel-btn {
  260. background-color: #ffffff;
  261. border: 1px solid #e2e8f0;
  262. color: #64748b;
  263. }
  264. .cancel-btn:hover {
  265. background-color: #f8fafc;
  266. }
  267. .confirm-btn {
  268. background-color: #3b82f6;
  269. border: none;
  270. color: white;
  271. }
  272. .confirm-btn:hover {
  273. background-color: #2563eb;
  274. }
  275. .modal-main {
  276. display: grid;
  277. grid-template-columns: 1fr 1fr;
  278. }
  279. @keyframes fadeIn {
  280. from { opacity: 0; transform: scale(0.95); }
  281. to { opacity: 1; transform: scale(1); }
  282. }
  283. </style>