@@ -125,9 +125,16 @@ export function createMapProjection(deps: MapProjectionDeps) {
125125 { value : 'gather' , name : '集结' , label : '📣 集结此处' , color : '#22c55e' } ,
126126 { value : 'scout' , name : '侦查' , label : '👁 侦查此处' , color : '#f59e0b' } ,
127127 { value : 'danger' , name : '危险' , label : '⚠ 危险区域' , color : '#f97316' } ,
128+ { value : 'custom' , name : '自定义' , label : '📍 自定义标点' , color : '#0ea5e9' } ,
128129 ] ;
129130 }
130131
132+ function sanitizeCustomTacticalLabel ( raw : unknown ) {
133+ const text = String ( raw || '' ) . trim ( ) ;
134+ if ( ! text ) return '📍 自定义标点' ;
135+ return text . slice ( 0 , 64 ) ;
136+ }
137+
131138 function closeTacticalMenu ( ) {
132139 if ( tacticalMenuOutsideClickHandler ) {
133140 document . removeEventListener ( 'mousedown' , tacticalMenuOutsideClickHandler , true ) ;
@@ -261,6 +268,10 @@ export function createMapProjection(deps: MapProjectionDeps) {
261268 <span>标注类型</span>
262269 <select class="nmc-tactical-type"></select>
263270 </label>
271+ <label class="nmc-tactical-row nmc-tactical-label-row" style="display:none;">
272+ <span>标点文字</span>
273+ <input class="nmc-tactical-label-input" type="text" maxlength="64" placeholder="例如:📌 这里集合" />
274+ </label>
264275 <label class="nmc-tactical-row">
265276 <span>过期时间</span>
266277 <select class="nmc-tactical-ttl">
@@ -285,12 +296,14 @@ export function createMapProjection(deps: MapProjectionDeps) {
285296 ` ;
286297
287298 const typeSelect = menu . querySelector ( '.nmc-tactical-type' ) as HTMLSelectElement | null ;
299+ const customLabelRow = menu . querySelector ( '.nmc-tactical-label-row' ) as HTMLElement | null ;
300+ const customLabelInput = menu . querySelector ( '.nmc-tactical-label-input' ) as HTMLInputElement | null ;
288301 const ttlSelect = menu . querySelector ( '.nmc-tactical-ttl' ) as HTMLSelectElement | null ;
289302 const customRow = menu . querySelector ( '.nmc-tactical-custom-row' ) as HTMLElement | null ;
290303 const customInput = menu . querySelector ( '.nmc-tactical-custom-ttl' ) as HTMLInputElement | null ;
291304 const confirmBtn = menu . querySelector ( '.nmc-tactical-confirm' ) as HTMLButtonElement | null ;
292305 const cancelBtn = menu . querySelector ( '.nmc-tactical-cancel' ) as HTMLButtonElement | null ;
293- if ( ! typeSelect || ! ttlSelect || ! customRow || ! customInput || ! confirmBtn || ! cancelBtn ) {
306+ if ( ! typeSelect || ! customLabelRow || ! customLabelInput || ! ttlSelect || ! customRow || ! customInput || ! confirmBtn || ! cancelBtn ) {
294307 return false ;
295308 }
296309
@@ -303,11 +316,37 @@ export function createMapProjection(deps: MapProjectionDeps) {
303316
304317 const syncPreviewFromSelection = ( ) => {
305318 const selectedType = typeOptions . find ( ( item ) => item . value === typeSelect . value ) || typeOptions [ 0 ] ;
306- upsertTacticalPreviewMarker ( map , worldPos , selectedType ) ;
319+ const isCustomType = selectedType . value === 'custom' ;
320+ const previewType = isCustomType
321+ ? {
322+ ...selectedType ,
323+ label : sanitizeCustomTacticalLabel ( customLabelInput . value ) ,
324+ }
325+ : selectedType ;
326+ upsertTacticalPreviewMarker ( map , worldPos , previewType ) ;
327+ } ;
328+
329+ const syncCustomLabelVisibility = ( ) => {
330+ const selectedType = typeOptions . find ( ( item ) => item . value === typeSelect . value ) || typeOptions [ 0 ] ;
331+ const isCustomType = selectedType . value === 'custom' ;
332+ customLabelRow . style . display = isCustomType ? 'flex' : 'none' ;
333+ if ( isCustomType && ! customLabelInput . value . trim ( ) ) {
334+ customLabelInput . value = selectedType . label ;
335+ }
307336 } ;
337+
338+ syncCustomLabelVisibility ( ) ;
308339 syncPreviewFromSelection ( ) ;
309340
310341 typeSelect . addEventListener ( 'change' , ( ) => {
342+ syncCustomLabelVisibility ( ) ;
343+ syncPreviewFromSelection ( ) ;
344+ if ( typeSelect . value === 'custom' ) {
345+ customLabelInput . focus ( ) ;
346+ }
347+ } ) ;
348+
349+ customLabelInput . addEventListener ( 'input' , ( ) => {
311350 syncPreviewFromSelection ( ) ;
312351 } ) ;
313352
@@ -326,6 +365,9 @@ export function createMapProjection(deps: MapProjectionDeps) {
326365
327366 confirmBtn . addEventListener ( 'click' , ( ) => {
328367 const selectedType = typeOptions . find ( ( item ) => item . value === typeSelect . value ) || typeOptions [ 0 ] ;
368+ const finalLabel = selectedType . value === 'custom'
369+ ? sanitizeCustomTacticalLabel ( customLabelInput . value )
370+ : selectedType . label ;
329371 const ttl = resolveTtlFromMenuValue ( ttlSelect . value , customInput . value ) ;
330372 if ( ! ttl ) {
331373 customInput . focus ( ) ;
@@ -336,7 +378,7 @@ export function createMapProjection(deps: MapProjectionDeps) {
336378 deps . onCreateTacticalWaypoint ( {
337379 x : worldPos . x ,
338380 z : worldPos . z ,
339- label : selectedType . label ,
381+ label : finalLabel ,
340382 tacticalType : selectedType . value ,
341383 color : selectedType . color ,
342384 ttlSeconds : ttl . ttlSeconds ,
0 commit comments