1import { Controller } from "@hotwired/stimulus" 2 3// å¯¹åº app/views/dashboard/_modals.html.erb 4export default class extends Controller { 5 static targets = [ 6 "productCreateModal", "productEditModal", "productDeleteModal", 7 "materialCreateModal", "materialDeleteModal", 8 "voiceModal", 9 "backdrop" 10 ] 11 static values = { 12 redirectUrl: String, 13 currentProductId: Number, 14 currentMaterialId: Number, 15 deleteProductConfirm: String 16 } 17 18 connect() { 19 console.log("Dashboard modals controller connected") 20 this.setupKeyboardHandlers() 21 } 22 23 disconnect() { 24 this.removeKeyboardHandlers() 25 } 26 27 // 设置é®çäºä»¶çå¬ 28 setupKeyboardHandlers() { 29 this.boundHandleKeydown = this.handleKeydown.bind(this) 30 document.addEventListener('keydown', this.boundHandleKeydown) 31 } 32 33 // ç§»é¤é®çäºä»¶çå¬ 34 removeKeyboardHandlers() { 35 if (this.boundHandleKeydown) { 36 document.removeEventListener('keydown', this.boundHandleKeydown) 37 } 38 } 39 40 // å¤çé®çäºä»¶ 41 handleKeydown(event) { 42 if (event.key === 'Escape') { 43 this.closeAllModals() 44 } 45 } 46 47 // å ³éæææ¨¡ææ¡ 48 closeAllModals() { 49 // 产åç¸å ³æ¨¡ææ¡ 50 if (this.hasProductCreateModalTarget) this.hideModal(this.productCreateModalTarget) 51 if (this.hasProductEditModalTarget) this.hideModal(this.productEditModalTarget) 52 if (this.hasProductDeleteModalTarget) this.hideModal(this.productDeleteModalTarget) 53 54 // ææç¸å ³æ¨¡ææ¡ 55 if (this.hasMaterialCreateModalTarget) this.hideModal(this.materialCreateModalTarget) 56 if (this.hasMaterialDeleteModalTarget) this.hideModal(this.materialDeleteModalTarget) 57 58 // è¯é³æ¨¡ææ¡ 59 if (this.hasVoiceModalTarget) this.hideModal(this.voiceModalTarget) 60 } 61 62 // æ¾ç¤ºæ¨¡ææ¡çéç¨æ¹æ³ 63 showModal(modalTarget) { 64 if (modalTarget) { 65 modalTarget.classList.remove('hidden') 66 modalTarget.classList.add('flex') 67 68 // èªå¨èç¦å°ç¬¬ä¸ä¸ªè¾å ¥æ¡ 69 const firstInput = modalTarget.querySelector('input, textarea') 70 if (firstInput) { 71 setTimeout(() => firstInput.focus(), 100) 72 } 73 } 74 } 75 76 // éèæ¨¡ææ¡çéç¨æ¹æ³ 77 hideModal(modalTarget) { 78 if (modalTarget) { 79 modalTarget.classList.add('hidden') 80 modalTarget.classList.remove('flex') 81 82 // æ¸ ç表å 83 const form = modalTarget.querySelector('form') 84 if (form) { 85 form.reset() 86 this.clearFormErrors(form) 87 } 88 } 89 } 90 91 // æ¸ ç表åé误 92 clearFormErrors(form) { 93 // ç§»é¤éè¯¯æ ·å¼ 94 const errorInputs = form.querySelectorAll('.border-danger-300') 95 errorInputs.forEach(input => { 96 input.classList.remove('border-danger-300') 97 input.classList.add('border-surface-300') 98 }) 99 100 // ç§»é¤éè¯¯æ¶æ¯ 101 const errorMessages = form.querySelectorAll('.text-danger-600') 102 errorMessages.forEach(msg => msg.remove()) 103 } 104 105 // === 产åç¸å ³æ¨¡ææ¡æ¹æ³ === 106 107 // æ¾ç¤ºåå»ºäº§åæ¨¡ææ¡ 108 showProductCreateModal() { 109 if (this.hasProductCreateModalTarget) { 110 this.showModal(this.productCreateModalTarget) 111 } 112 } 113 114 // éèåå»ºäº§åæ¨¡ææ¡ 115 hideProductCreateModal() { 116 if (this.hasProductCreateModalTarget) { 117 this.hideModal(this.productCreateModalTarget) 118 } 119 } 120 121 // æ¾ç¤ºç¼è¾äº§åæ¨¡ææ¡ 122 showProductEditModal(productId, productName) { 123 this.currentProductIdValue = productId 124 125 if (this.hasProductEditModalTarget) { 126 // å¡«å è¡¨åæ°æ® 127 const nameInput = this.productEditModalTarget.querySelector('input[name="product[name]"]') 128 if (nameInput) nameInput.value = productName 129 130 this.showModal(this.productEditModalTarget) 131 } 132 } 133 134 // éèç¼è¾äº§åæ¨¡ææ¡ 135 hideProductEditModal() { 136 if (this.hasProductEditModalTarget) { 137 this.hideModal(this.productEditModalTarget) 138 } 139 } 140 141 // æ¾ç¤ºå é¤äº§åç¡®è®¤æ¨¡ææ¡ 142 showProductDeleteModal(productId, productName) { 143 this.currentProductIdValue = productId 144 145 if (this.hasProductDeleteModalTarget) { 146 // æ´æ°ç¡®è®¤æ¶æ¯ 147 const confirmMessage = this.productDeleteModalTarget.querySelector('[data-confirm-message]') 148 if (confirmMessage) { 149 confirmMessage.textContent = this.deleteProductConfirmValue.replace('%{name}', productName) 150 } 151 152 this.showModal(this.productDeleteModalTarget) 153 } 154 } 155 156 // éèå é¤äº§åç¡®è®¤æ¨¡ææ¡ 157 hideProductDeleteModal() { 158 if (this.hasProductDeleteModalTarget) { 159 this.hideModal(this.productDeleteModalTarget) 160 } 161 } 162 163 // === ææç¸å ³æ¨¡ææ¡æ¹æ³ === 164 165 // æ¾ç¤ºåå»ºæææ¨¡ææ¡ 166 showMaterialCreateModal(productId) { 167 this.currentProductIdValue = productId 168 169 if (this.hasMaterialCreateModalTarget) { 170 this.showModal(this.materialCreateModalTarget) 171 } 172 } 173 174 // éèåå»ºæææ¨¡ææ¡ 175 hideMaterialCreateModal() { 176 if (this.hasMaterialCreateModalTarget) { 177 this.hideModal(this.materialCreateModalTarget) 178 } 179 } 180 181 // æ¾ç¤ºå 餿æç¡®è®¤æ¨¡ææ¡ 182 showMaterialDeleteModal(materialId) { 183 this.currentMaterialIdValue = materialId 184 185 if (this.hasMaterialDeleteModalTarget) { 186 this.showModal(this.materialDeleteModalTarget) 187 } 188 } 189 190 // éèå 餿æç¡®è®¤æ¨¡ææ¡ 191 hideMaterialDeleteModal() { 192 if (this.hasMaterialDeleteModalTarget) { 193 this.hideModal(this.materialDeleteModalTarget) 194 } 195 } 196 197 // === è¯é³æ¨¡ææ¡æ¹æ³ === 198 199 // æ¾ç¤ºè¯é³è®¾ç½®æ¨¡ææ¡ 200 showVoiceModal() { 201 if (this.hasVoiceModalTarget) { 202 this.showModal(this.voiceModalTarget) 203 } 204 } 205 206 // éèè¯é³è®¾ç½®æ¨¡ææ¡ 207 hideVoiceModal() { 208 if (this.hasVoiceModalTarget) { 209 this.hideModal(this.voiceModalTarget) 210 } 211 } 212 213 // === 表åæäº¤å¤ç === 214 215 // å¤ç表åæäº¤ç»æäºä»¶ 216 handleSubmitEnd(event) { 217 const response = event.detail.fetchResponse 218 const form = event.target 219 220 if (response.ok) { 221 // æåæ¶å ³éç¸å ³æ¨¡ææ¡å¹¶éå®å 222 this.closeModalByForm(form) 223 224 if (this.redirectUrlValue) { 225 window.location.href = this.redirectUrlValue 226 } 227 } else { 228 // å¤±è´¥æ¶æ¾ç¤ºéè¯¯ä¿¡æ¯ 229 this.handleFormErrors(form, response) 230 } 231 } 232 233 // æ ¹æ®è¡¨åå ³é对åºçæ¨¡ææ¡ 234 closeModalByForm(form) { 235 const modal = form.closest('[data-dashboard-modals-target]') 236 if (modal) { 237 this.hideModal(modal) 238 } 239 } 240 241 // å¤ç表åé误 242 async handleFormErrors(form, response) { 243 try { 244 const data = await response.json() 245 246 if (data.errors) { 247 this.displayFormErrors(form, data.errors) 248 } 249 } catch (error) { 250 console.error('Failed to parse error response:', error) 251 } 252 } 253 254 // æ¾ç¤ºè¡¨åé误 255 displayFormErrors(form, errors) { 256 // å æ¸ çä¹åçé误 257 this.clearFormErrors(form) 258
259 Object.keys(errors).forEach(field => { 260 const input = form.querySelector(`[name*="[${field}]"]`) 261 if (input) { 262 // æ·»å éè¯¯æ ·å¼ 263 input.classList.remove('border-surface-300') 264 input.classList.add('border-danger-300') 265 266 // æ·»å éè¯¯æ¶æ¯ 267 const errorMessage = document.createElement('p') 268 errorMessage.className = 'mt-1 text-sm text-danger-600' 269 errorMessage.textContent = errors[field][0] 270 271 input.parentNode.appendChild(errorMessage) 272 } 273 }) 274 } 275 276 // ç¹å»èæ¯å ³éæ¨¡ææ¡ 277 clickOutside(event) { 278 if (event.target === event.currentTarget) { 279 this.closeAllModals() 280 } 281 } 282}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.