- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경 - DB 연결 하드코딩 → .env 기반으로 변경 - MySQL strict mode DATE 오류 수정
1598 lines
72 KiB
PHP
1598 lines
72 KiB
PHP
<?php
|
||
require_once($_SERVER['DOCUMENT_ROOT'] . "/session.php");
|
||
|
||
if (!isset($_SESSION["level"]) || $_SESSION["level"] > 5) {
|
||
sleep(1);
|
||
header("Location:" . $WebSite . "login/login_form.php");
|
||
exit;
|
||
}
|
||
|
||
include $_SERVER['DOCUMENT_ROOT'] . '/load_header.php';
|
||
|
||
// 첫 화면 표시 문구
|
||
$title_message = '절곡바라시 기초자료';
|
||
?>
|
||
|
||
<title> <?=$title_message?> </title>
|
||
<link rel="stylesheet" href="css/style.css?v=<?=time()?>">
|
||
</head>
|
||
<body>
|
||
<?php
|
||
$option = isset($_REQUEST['option']) ? $_REQUEST['option'] : '';
|
||
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
|
||
$mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : '';
|
||
$header = isset($_REQUEST['header']) ? $_REQUEST['header'] : '';
|
||
$partIndex = isset($_REQUEST['partIndex']) ? $_REQUEST['partIndex'] : '';
|
||
$tablename = 'bending';
|
||
|
||
require_once($_SERVER['DOCUMENT_ROOT'] . "/lib/mydb.php");
|
||
$pdo = db_connect();
|
||
|
||
$num = isset($_REQUEST["num"]) ? $_REQUEST["num"] : 0;
|
||
|
||
// 조회일 경우
|
||
if ($num > 0 && $mode == 'view' ) {
|
||
try {
|
||
$sql = "SELECT * FROM " . $DB . "." . $tablename . " WHERE num=?";
|
||
$stmh = $pdo->prepare($sql);
|
||
$stmh->bindValue(1, $num, PDO::PARAM_STR);
|
||
$stmh->execute();
|
||
|
||
$row = $stmh->fetch(PDO::FETCH_ASSOC);
|
||
include '_row.php';
|
||
|
||
} catch (PDOException $Exception) {
|
||
print "오류: " . $Exception->getMessage();
|
||
}
|
||
|
||
$mode = 'view';
|
||
$title_message = '절곡바라시 기초자료 조회';
|
||
|
||
}
|
||
else if ($num > 0 && $mode == 'modify' ) {
|
||
try {
|
||
$sql = "SELECT * FROM " . $DB . "." . $tablename . " WHERE num=?";
|
||
$stmh = $pdo->prepare($sql);
|
||
$stmh->bindValue(1, $num, PDO::PARAM_STR);
|
||
$stmh->execute();
|
||
|
||
$row = $stmh->fetch(PDO::FETCH_ASSOC);
|
||
include '_row.php';
|
||
|
||
} catch (PDOException $Exception) {
|
||
print "오류: " . $Exception->getMessage();
|
||
}
|
||
|
||
$mode = 'modify';
|
||
$title_message = '절곡바라시 기초자료 수정';
|
||
|
||
}
|
||
else if ($num > 0 && $mode == 'copy' ) {
|
||
|
||
try {
|
||
$sql = "SELECT * FROM " . $DB . "." . $tablename . " WHERE num=?";
|
||
$stmh = $pdo->prepare($sql);
|
||
$stmh->bindValue(1, $num, PDO::PARAM_STR);
|
||
$stmh->execute();
|
||
|
||
$row = $stmh->fetch(PDO::FETCH_ASSOC);
|
||
include '_row.php';
|
||
|
||
} catch (PDOException $Exception) {
|
||
print "오류: " . $Exception->getMessage();
|
||
}
|
||
|
||
$title_message = '(데이터복사) 절곡바라시 기초자료 등록';
|
||
}
|
||
else if ($mode == 'write' ) { // 전달받은 새로 작성하는 경우
|
||
|
||
include '_request.php';
|
||
|
||
$imgdata = '';
|
||
|
||
// URL 파라미터에서 배열 데이터 받기
|
||
$inputList = isset($_REQUEST['inputList']) ? json_decode($_REQUEST['inputList'], true) : [];
|
||
$bendingrateList = isset($_REQUEST['bendingrateList']) ? json_decode($_REQUEST['bendingrateList'], true) : [];
|
||
$sumList = isset($_REQUEST['sumList']) ? json_decode($_REQUEST['sumList'], true) : [];
|
||
$colorList = isset($_REQUEST['colorList']) ? json_decode($_REQUEST['colorList'], true) : [];
|
||
$AList = isset($_REQUEST['AList']) ? json_decode($_REQUEST['AList'], true) : [];
|
||
|
||
// 이미지는 부모창의 번호를 기준으로 가져온다.
|
||
|
||
if(intval($num) > 0) {
|
||
$sql = "SELECT * FROM " . $DB . "." . $tablename . " WHERE num=?";
|
||
$stmh = $pdo->prepare($sql);
|
||
$stmh->bindValue(1, $num, PDO::PARAM_STR);
|
||
$stmh->execute();
|
||
|
||
$row = $stmh->fetch(PDO::FETCH_ASSOC);
|
||
$imgdata = $row['imgdata'];
|
||
}
|
||
|
||
// 부모창에서 전달받은 partIndex (부품 인덱스)
|
||
$partIndex = isset($_REQUEST['partIndex']) ? $_REQUEST['partIndex'] : '';
|
||
$author = isset($user_name) ? $user_name : '';
|
||
$registration_date = date('Y-m-d'); // 현재일자 기록
|
||
|
||
$title_message = '(데이터 다른이름으로 저장) 절곡바라시 기초자료 등록';
|
||
|
||
} else {
|
||
include '_request.php';
|
||
|
||
$mode = 'insert';
|
||
$memo = '';
|
||
$imgdata = '';
|
||
$itemName = '';
|
||
|
||
$inputList = [];
|
||
$bendingrateList = [];
|
||
$sumList = [];
|
||
$colorList = [];
|
||
$AList = [];
|
||
|
||
$author = $user_name ?? '';
|
||
$registration_date = date('Y-m-d'); // 현재일자 기록
|
||
|
||
// mode값에 get 이면 각 부품에서 이 코드를 호출하는 것이다.
|
||
if($mode == 'get') {
|
||
// 삽입으로 설정함.
|
||
$mode = 'insert';
|
||
$item_sep = isset($_REQUEST['item_sep']) ? $_REQUEST['item_sep'] : '';
|
||
$model_UA = isset($_REQUEST['model_UA']) ? $_REQUEST['model_UA'] : '';
|
||
$item_bending = isset($_REQUEST['item_bending']) ? $_REQUEST['item_bending'] : '';
|
||
$itemName = isset($_REQUEST['itemName']) ? $_REQUEST['itemName'] : '';
|
||
$item_spec = isset($_REQUEST['item_spec']) ? $_REQUEST['item_spec'] : '';
|
||
$imgdata = isset($_REQUEST['imgdata']) ? $_REQUEST['imgdata'] : '';
|
||
$exit_direction = isset($_REQUEST['exit_direction']) ? $_REQUEST['exit_direction'] : '';
|
||
$front_bottom_width = isset($_REQUEST['front_bottom_width']) ? $_REQUEST['front_bottom_width'] : '';
|
||
$rail_width = isset($_REQUEST['rail_width']) ? $_REQUEST['rail_width'] : '';
|
||
$box_width = isset($_REQUEST['box_width']) ? $_REQUEST['box_width'] : '';
|
||
$box_height = isset($_REQUEST['box_height']) ? $_REQUEST['box_height'] : '';
|
||
}
|
||
|
||
}
|
||
|
||
if(empty($author)) {
|
||
$author = $user_name ?? '';
|
||
}
|
||
?>
|
||
|
||
<form id="board_form" name="board_form" method="post" enctype="multipart/form-data">
|
||
|
||
<input type="hidden" id="mode" name="mode" value="<?=$mode?>">
|
||
<input type="hidden" id="num" name="num" value=<?=$num?>>
|
||
<input type="hidden" id="tablename" name="tablename" value=<?=$tablename?>>
|
||
<input type="hidden" id="update_log" name="update_log" value=<?=$update_log?>>
|
||
<input type="hidden" id="header" name="header" value="<?=$header?>">
|
||
<input type="hidden" id="imgdata" name="imgdata" value="<?=$imgdata?>">
|
||
|
||
<div class="container-fluid">
|
||
<div class="card">
|
||
<div class="card-header text-center">
|
||
<div class="d-flex p-1 mb-1 justify-content-center align-items-center ">
|
||
<h4><?=$title_message?></h4>
|
||
<?php if($mode =='view') { ?>
|
||
<button type="button" class="btn btn-dark btn-sm me-1" onclick="location.href='write_form.php?mode=modify&num=<?=$num?>&tablename=<?=$tablename?>';"> <i class="bi bi-pencil-square"></i> 수정 </button>
|
||
<button id="copyBtn" class="btn btn-primary btn-sm me-1" type="button"><i class="bi bi-copy"></i> 복사</button>
|
||
<button id="deleteBtn" class="btn btn-danger btn-sm me-1" type="button"><i class="bi bi-trash2"></i> 삭제</button>
|
||
<?php } ?>
|
||
<?php if($mode!=='view') { ?>
|
||
<button id="saveBtn" class="btn btn-dark btn-sm me-1 " type="button">
|
||
<? if((int)$num>0) print ' <i class="bi bi-hdd-fill"></i> 저장'; else print ' <i class="bi bi-hdd-fill"></i> 저장'; ?></button>
|
||
<?php } ?>
|
||
<button type="button" class="btn btn-outline-dark btn-sm me-2" id="showlogBtn"> H</button>
|
||
<button class="btn btn-secondary btn-sm ms-3" onclick="self.close();"> × 닫기 </button>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="container-fluid">
|
||
<div class="card">
|
||
<div class="card-body">
|
||
<div class="row mt-3">
|
||
<div class="col-sm-9">
|
||
<div class="card">
|
||
<div class="card-header text-center">
|
||
<table class="table table-bordered align-middle">
|
||
<tr>
|
||
<!-- 1행: 등록일, SLAT형태, 절곡품 그룹 -->
|
||
<td class="text-end align-middle fw-bold" style="width: 8%;">등록일</td>
|
||
<td style="width: 13%;">
|
||
<input type="date" id="registration_date" class="form-control noborder-input w110px" name="registration_date" value="<?= $registration_date ?>" autocomplete="off">
|
||
</td>
|
||
<td class="text-end align-middle fw-bold" style="width: 8%;">형태</td>
|
||
<td class="w250px">
|
||
<div class="d-flex justify-content-start">
|
||
<input type="radio" id="screenUse" name="item_sep" value="스크린" <?php if (empty($item_sep) || $item_sep == '스크린') echo 'checked'; ?> class="me-1">
|
||
<label for="screenUse" class="me-2">스크린</label>
|
||
<input type="radio" id="steelStrutUse" name="item_sep" value="철재" <?php if ($item_sep == '철재') echo 'checked'; ?> class="me-1">
|
||
<label for="steelStrutUse">철재</label>
|
||
</div>
|
||
</td>
|
||
<td class="text-end align-middle fw-bold" style="width: 10%;">인정/비인정</td>
|
||
<td class="w150px">
|
||
<input type="radio" id="screenUseUA" name="model_UA" value="인정" <?php if (empty($model_UA) || $model_UA == '인정') echo 'checked'; ?> class="me-1">
|
||
<label for="screenUseUA" class="me-2">인정</label>
|
||
<input type="radio" id="steelStrutUseUA" name="model_UA" value="비인정" <?php if ($model_UA == '비인정') echo 'checked'; ?> class="me-1">
|
||
<label for="steelStrutUseUA">비인정</label>
|
||
</td>
|
||
<td class="text-end align-middle fw-bold" style="width: 8%;">절곡품 그룹</td>
|
||
<td colspan="1" class="text-start">
|
||
<?php
|
||
// 예: getCategoryByName($parentName) 함수가 존재하며,
|
||
// '절곡물'이라는 2단계 카테고리의 자식(3단계) 카테고리 이름 배열을 리턴한다.
|
||
$l3_list = getCategoryByName('스크린','절곡물');
|
||
?>
|
||
<select id="item_bending" name="item_bending" class="form-select w-auto mx-1" style="font-size: 0.7rem; height: 30px;">
|
||
<option value="">(절곡물)</option>
|
||
<?php foreach($l3_list as $itemVal): ?>
|
||
<option
|
||
value="<?= htmlspecialchars($itemVal, ENT_QUOTES, 'UTF-8') ?>"
|
||
<?= ($item_bending === $itemVal) ? 'selected' : '' ?>>
|
||
<?= htmlspecialchars($itemVal, ENT_QUOTES, 'UTF-8') ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="text-end align-middle fw-bold w50px">폭합</td>
|
||
<td colspan="1">
|
||
<input type="text" id="widthsum" class="form-control text-end w50px" name="widthsum" value="<?= $widthsum ?>" readonly autocomplete="off">
|
||
</td>
|
||
<td class="text-end align-middle fw-bold">품명</td>
|
||
<td>
|
||
<input type="text" id="itemName" class="form-control noborder-input text-start" name="itemName" value="<?= $itemName ?>" autocomplete="off">
|
||
</td>
|
||
<td class="text-end align-middle fw-bold">규격(가로*세로)</td>
|
||
<td>
|
||
<input type="text" id="item_spec" class="form-control noborder-input text-start " name="item_spec" value="<?= $item_spec ?>" autocomplete="off">
|
||
</td>
|
||
<td class="text-end align-middle fw-bold">재질</td>
|
||
<td>
|
||
<select id="material" class="form-select w-auto" name="material" style="font-size: 0.7rem; height: 30px;">
|
||
<?php
|
||
$options = ['(선택)', 'EGI 1.15T', 'EGI 1.55T', 'SUS 1.2T', 'SUS 1.5T'];
|
||
foreach ($options as $option) {
|
||
$selected = ($material === $option) ? 'selected' : '';
|
||
echo "<option value=\"" . htmlspecialchars($option, ENT_QUOTES, 'UTF-8') . "\" $selected>$option</option>";
|
||
}
|
||
?>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan="8" class="text-end ">
|
||
<div class="d-flex justify-content-start align-items-center">
|
||
(케이스 부품관련 영역) <span class="text-dark ms-5 me-2"></span>점검구 방향</span>
|
||
<select id="exit_direction" class="form-select w120px" name="exit_direction" style="font-size: 0.7rem; height: 30px;">
|
||
<option value="">(선택)</option>
|
||
<option value="양면 점검구" <?php if ($exit_direction === '양면 점검구') echo 'selected'; ?>>양면 점검구</option>
|
||
<option value="후면 점검구" <?php if ($exit_direction === '후면 점검구') echo 'selected'; ?>>후면 점검구</option>
|
||
<option value="밑면 점검구" <?php if ($exit_direction === '밑면 점검구') echo 'selected'; ?>>밑면 점검구</option>
|
||
</select>
|
||
<span class="text-dark ms-3 me-1">케이스 너비</span>
|
||
<input type="text" id="box_width" class="form-control text-end w50px" name="box_width" value="<?= $box_width ?>" autocomplete="off">
|
||
<span class="text-dark ms-3 me-1">케이스 높이</span>
|
||
<input type="text" id="box_height" class="form-control text-end w50px" name="box_height" value="<?= $box_height ?>" autocomplete="off">
|
||
<span class="text-dark ms-3 me-1">전면부 밑 치수</span>
|
||
<input type="text" id="front_bottom_width" class="form-control text-end w50px" name="front_bottom_width" value="<?= $front_bottom_width ?>" autocomplete="off">
|
||
<span class="text-dark ms-3 me-1">레일폭</span>
|
||
<input type="text" id="rail_width" class="form-control text-end w50px" name="rail_width" value="<?= $rail_width ?>" autocomplete="off">
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td class="text-end align-middle fw-bold">작성자</td>
|
||
<td colspan="1">
|
||
<input type="text" id="author" class="form-control noborder-input text-start" name="author" value="<?= $author ?>" autocomplete="off">
|
||
</td>
|
||
<td class="text-end align-middle fw-bold">품목 검색어</td>
|
||
<td colspan="1">
|
||
<input type="text" id="search_keyword" class="form-control noborder-input text-start" name="search_keyword" value="<?= $search_keyword ?>" autocomplete="off">
|
||
</td>
|
||
<td class="text-end align-middle fw-bold">비고</td>
|
||
<td colspan="3">
|
||
<input type="text" id="memo" class="form-control noborder-input text-start" name="memo" value="<?= $memo ?>" autocomplete="off">
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="table-container">
|
||
<table class="table table-bordered" id="dynamicTable">
|
||
<tbody id="tableBody">
|
||
<!-- 기본적으로 빈 상태로 두고, JS로 데이터를 추가 -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="d-flex mt-2 mb-2">
|
||
<button type="button" class="btn btn-secondary btn-sm me-2" id="emptyColumnBtn">모든칸 비우기</button>
|
||
<button type="button" class="btn btn-success btn-sm me-2" id="addColumnBtn">마지막 열추가</button>
|
||
<button type="button" class="btn btn-danger btn-sm" id="removeColumnBtn">마자막 열삭제</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-sm-3" style="padding : 4px;">
|
||
<!-- 그리기 컨트롤 -->
|
||
<div class="d-flex align-items-center mb-2" style="width:260px;">
|
||
<?php if($mode!=='view') : ?>
|
||
<button id="openEditorBtn" type="button" class="btn btn-outline-dark btn-sm me-2 viewNoBtn">그리기</button>
|
||
<?php else: ?>
|
||
<div class="text-muted">
|
||
<i class="bi bi-info-circle"></i> 조회 모드, 그리기 기능 불가
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<!-- 이미지 표시 영역 (수정된) -->
|
||
<div class="card mb-3">
|
||
<div class="card-body">
|
||
<div class="image-container mb-3" style="position: relative;">
|
||
<?php
|
||
// bending 테이블의 imgdata 값 사용
|
||
$imgUrl = '';
|
||
|
||
if (!empty($imgdata)) {
|
||
// 이미지 파일이 실제로 존재하는지 확인
|
||
$imagePath = $_SERVER['DOCUMENT_ROOT'] . '/bending/img/' . $imgdata;
|
||
if (file_exists($imagePath)) {
|
||
$imgUrl = '/bending/img/' . $imgdata;
|
||
} else {
|
||
// 기존 경로도 확인
|
||
$imagePath2 = $_SERVER['DOCUMENT_ROOT'] . '/bending/img/' . $imgdata;
|
||
if (file_exists($imagePath2)) {
|
||
$imgUrl = '/bending/img/' . $imgdata;
|
||
}
|
||
}
|
||
}
|
||
?>
|
||
|
||
<?php if (!empty($imgUrl)): ?>
|
||
<img
|
||
id="targetImage"
|
||
src="<?= htmlspecialchars($imgUrl) ?>"
|
||
class="img-thumbnail img-fluid"
|
||
alt="편집할 이미지"
|
||
style="max-width:350px; cursor:pointer; display: block;"
|
||
>
|
||
<?php else: ?>
|
||
<p class="text-muted">이미지가 없습니다.</p>
|
||
<?php endif; ?>
|
||
|
||
<div class="paste-overlay" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 123, 255, 0.8); color: white; padding: 10px 15px; border-radius: 5px; z-index: 1000; text-align: center; font-size: 14px;">
|
||
<i class="bi bi-clipboard-plus"></i>
|
||
<span>Ctrl+V로 이미지 붙여넣기</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- 다이얼로그(편집기) 마크업: imageEditor.js 호출 시 참조 -->
|
||
<dialog id="editorDialog">
|
||
<div id="editorHeader">
|
||
<h5>이미지 편집기</h5>
|
||
<button id="applyBtn" class="btn btn-success btn-sm">적용하기</button>
|
||
<button type="button" class="btn-close" aria-label="닫기" id="closeEditorBtn"></button>
|
||
</div>
|
||
|
||
<!-- 툴바 -->
|
||
<div id="editorToolbar">
|
||
<button id="polyBtn" class="btn btn-outline-primary toolbar-btn" title="폴리라인"><i class="bi bi-vector-pen"></i></button>
|
||
<button id="freeBtn" class="btn btn-outline-primary toolbar-btn" title="자유선"><i class="bi bi-brush"></i></button>
|
||
<button id="lineBtn" class="btn btn-outline-primary toolbar-btn" title="직선 (L키)"><i class="bi bi-slash-lg"></i></button>
|
||
<button id="textBtn" class="btn btn-outline-primary toolbar-btn" title="문자입력"><i class="bi bi-type"></i></button>
|
||
<button id="eraserBtn" class="btn btn-outline-warning toolbar-btn" title="지우개"><i class="bi bi-eraser-fill"></i></button>
|
||
<button id="selectBtn" class="btn btn-outline-secondary toolbar-btn" title="객체선택"><i class="bi bi-cursor-text"></i></button>
|
||
<label class="form-check-label" style="font-size: 0.9em; font-weight: 500; cursor: pointer;">
|
||
<label style="font-size: 1.4em; font-weight: 500; cursor: pointer; margin-right:100px;">
|
||
<input class="form-check-input" type="checkbox" id="rightAngle" checked style="cursor: pointer; z-index: 10; position: relative;">
|
||
직각 고정
|
||
</label>
|
||
<button id="clearBtn" class="btn btn-outline-danger btn-sm ms-auto">전체 지우기</button>
|
||
</div>
|
||
|
||
<!-- 지우개 크기 & 색상 -->
|
||
<div id="editorToolbar2" class="d-flex align-items-center px-3 pb-2">
|
||
<div class="btn-group" role="group" id="colorPicker">
|
||
<button type="button" class="btn color-btn active" data-color="#000000" style="background:#000;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#ff0000" style="background:#f00;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#0000ff" style="background:#00f;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#00aa00" style="background:#0a0;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#ff8800" style="background:#f80;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#800080" style="background:#808;"></button>
|
||
<button type="button" class="btn color-btn" data-color="#888888" style="background:#888;"></button>
|
||
</div>
|
||
<label class="mb-0 me-2">지우개 크기</label>
|
||
<input type="range" class="form-range me-2" id="eraserRange" min="5" max="100" step="1" value="20">
|
||
<span id="eraserSizeLabel" class="fw-bold">20</span>px
|
||
</div>
|
||
|
||
<!-- 캔버스 영역 -->
|
||
<div id="editorBody">
|
||
<canvas id="c" width="370" height="300"></canvas>
|
||
</div>
|
||
</dialog>
|
||
|
||
|
||
<!-- 모듈 스크립트 -->
|
||
<script type="module">
|
||
console.log('=== 모듈 스크립트 로드 시작 ===');
|
||
|
||
let openImageEditor;
|
||
|
||
// 즉시 실행 함수로 감싸서 await 사용
|
||
(async function() {
|
||
try {
|
||
const module = await import('/js/imageEditor.js');
|
||
openImageEditor = module.openImageEditor;
|
||
console.log('openImageEditor 함수 import 완료:', typeof openImageEditor);
|
||
console.log('openImageEditor 함수:', openImageEditor);
|
||
|
||
// 전역 변수로 설정하여 다른 함수에서 접근 가능하도록 함
|
||
window.openImageEditor = openImageEditor;
|
||
console.log('openImageEditor를 전역 변수로 설정 완료');
|
||
} catch (error) {
|
||
console.error('모듈 로드 실패:', error);
|
||
return;
|
||
}
|
||
})();
|
||
|
||
// 전역 함수로 선언하여 loadForm에서 호출할 수 있도록 함
|
||
window.initializeImageEditor = function() {
|
||
console.log('=== initializeImageEditor 함수 시작 ===');
|
||
console.log('Fabric.js 확인:', typeof fabric);
|
||
console.log('fabric 객체:', fabric);
|
||
|
||
if (typeof fabric === 'undefined') {
|
||
console.error('Fabric.js가 로드되지 않았습니다!');
|
||
return;
|
||
}
|
||
const openBtn = document.getElementById('openEditorBtn');
|
||
const targetImg = document.getElementById('targetImage');
|
||
const imageContainer = document.querySelector('.image-container');
|
||
|
||
// null 체크 추가 (view 모드에서는 openBtn이 없을 수 있음)
|
||
if (!imageContainer) {
|
||
console.warn('이미지 편집기 초기화 실패: imageContainer를 찾을 수 없습니다.');
|
||
return;
|
||
}
|
||
|
||
// view 모드가 아닌 경우에만 openBtn과 targetImg 체크
|
||
const mode = '<?= $mode ?>';
|
||
if (mode !== 'view') {
|
||
if (!openBtn || !targetImg) {
|
||
console.warn('이미지 편집기 초기화 실패: 필요한 DOM 요소를 찾을 수 없습니다.');
|
||
console.log('openBtn:', openBtn);
|
||
console.log('targetImg:', targetImg);
|
||
console.log('imageContainer:', imageContainer);
|
||
return;
|
||
}
|
||
} else {
|
||
console.log('view 모드: 이미지 편집 기능 비활성화, 붙여넣기만 활성화');
|
||
// view 모드에서는 붙여넣기 기능만 활성화
|
||
document.addEventListener('paste', handlePaste);
|
||
console.log('view 모드에서 전역 붙여넣기 이벤트 리스너 등록됨');
|
||
return;
|
||
}
|
||
|
||
// pasteOverlay는 선택적 요소로 처리
|
||
const pasteOverlay = document.querySelector('.paste-overlay');
|
||
|
||
// 붙여넣기 이벤트 처리 함수
|
||
function handlePaste(event) {
|
||
console.log('붙여넣기 이벤트 발생:', event);
|
||
console.log('clipboardData:', event.clipboardData);
|
||
|
||
const items = (event.clipboardData || event.originalEvent?.clipboardData)?.items;
|
||
console.log('clipboard items:', items);
|
||
|
||
if (!items) {
|
||
console.log('클립보드 데이터가 없습니다.');
|
||
return;
|
||
}
|
||
|
||
for (let item of items) {
|
||
console.log('클립보드 아이템:', item.type);
|
||
if (item.type.indexOf('image') !== -1) {
|
||
console.log('이미지 발견, 처리 중...');
|
||
const blob = item.getAsFile();
|
||
const reader = new FileReader();
|
||
|
||
reader.onload = function(e) {
|
||
console.log('이미지 로드 완료');
|
||
|
||
// view 모드가 아닌 경우에만 targetImg 업데이트
|
||
const mode = '<?= $mode ?>';
|
||
if (mode !== 'view') {
|
||
if (targetImg) {
|
||
targetImg.src = e.target.result;
|
||
console.log('이미지가 붙여넣기되었습니다:', e.target.result.substring(0, 50) + '...');
|
||
}
|
||
} else {
|
||
console.log('view 모드: 이미지 붙여넣기 완료 (표시만)');
|
||
}
|
||
|
||
// 붙여넣기 성공 피드백
|
||
if (pasteOverlay) {
|
||
showPasteFeedback(true);
|
||
}
|
||
};
|
||
|
||
reader.onerror = function(e) {
|
||
console.error('이미지 로드 실패:', e);
|
||
};
|
||
|
||
reader.readAsDataURL(blob);
|
||
event.preventDefault();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 붙여넣기 피드백 표시
|
||
function showPasteFeedback(success = false) {
|
||
if (!pasteOverlay) {
|
||
console.log('pasteOverlay 요소가 없습니다.');
|
||
return;
|
||
}
|
||
|
||
if (success) {
|
||
pasteOverlay.innerHTML = '<i class="bi bi-check-circle"></i><span>이미지가 붙여넣기되었습니다!</span>';
|
||
pasteOverlay.style.background = 'rgba(40, 167, 69, 0.8)';
|
||
} else {
|
||
pasteOverlay.innerHTML = '<i class="bi bi-clipboard-plus"></i><span>Ctrl+V로 이미지 붙여넣기</span>';
|
||
pasteOverlay.style.background = 'rgba(0, 123, 255, 0.8)';
|
||
}
|
||
|
||
pasteOverlay.style.display = 'block';
|
||
pasteOverlay.classList.add('show');
|
||
setTimeout(() => {
|
||
pasteOverlay.classList.remove('show');
|
||
setTimeout(() => {
|
||
pasteOverlay.style.display = 'none';
|
||
}, 300);
|
||
}, 2000);
|
||
}
|
||
|
||
// 기존 이벤트 리스너 제거 (중복 방지)
|
||
document.removeEventListener('paste', handlePaste);
|
||
imageContainer.removeEventListener('click', imageContainerClickHandler);
|
||
imageContainer.removeEventListener('focus', imageContainerFocusHandler);
|
||
imageContainer.removeEventListener('blur', imageContainerBlurHandler);
|
||
openBtn.removeEventListener('click', openBtnClickHandler);
|
||
|
||
// 이벤트 핸들러 함수들
|
||
function imageContainerClickHandler() {
|
||
imageContainer.focus();
|
||
}
|
||
|
||
function imageContainerFocusHandler() {
|
||
showPasteFeedback();
|
||
}
|
||
|
||
function imageContainerBlurHandler() {
|
||
pasteOverlay.classList.remove('show');
|
||
}
|
||
|
||
function openBtnClickHandler() {
|
||
console.log('그리기 버튼이 클릭되었습니다.');
|
||
console.log('openImageEditor 함수 타입:', typeof openImageEditor);
|
||
console.log('openImageEditor 함수:', openImageEditor);
|
||
|
||
let src = targetImg ? targetImg.src : '';
|
||
console.log('편집할 이미지 소스:', src);
|
||
|
||
// 이미지가 없거나 placeholder인 경우 빈 문자열로 설정
|
||
if (!src || src.includes('placeholder') || src === 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2Y5ZjlmOSIvPjwvc3ZnPg==') {
|
||
console.log('이미지가 없거나 placeholder, 새로운 빈 캔버스로 시작');
|
||
src = '';
|
||
}
|
||
|
||
if (typeof openImageEditor !== 'function') {
|
||
console.error('openImageEditor가 함수가 아닙니다!');
|
||
console.error('openImageEditor 값:', openImageEditor);
|
||
return;
|
||
}
|
||
|
||
console.log('openImageEditor 함수 호출 시도...');
|
||
try {
|
||
const result = openImageEditor(src);
|
||
console.log('openImageEditor 호출 결과:', result);
|
||
console.log('result 타입:', typeof result);
|
||
|
||
if (result && typeof result.then === 'function') {
|
||
console.log('Promise 반환됨, then 체인 시작...');
|
||
result.then(newUrl => {
|
||
// 편집된 이미지를 화면에 반영
|
||
console.log('편집 완료, 새로운 이미지 URL:', newUrl);
|
||
if (targetImg) {
|
||
targetImg.src = newUrl;
|
||
}
|
||
}).catch(err => {
|
||
// 사용자가 취소했을 때
|
||
console.log('편집 취소:', err.message);
|
||
console.error('편집 오류 상세:', err);
|
||
});
|
||
} else {
|
||
console.error('openImageEditor가 Promise를 반환하지 않습니다!');
|
||
}
|
||
} catch (error) {
|
||
console.error('openImageEditor 호출 중 오류:', error);
|
||
console.error('오류 스택:', error.stack);
|
||
}
|
||
}
|
||
|
||
// 그리기 관련 코드
|
||
// 붙여넣기 이벤트 리스너 추가 (전역 이벤트)
|
||
document.addEventListener('paste', handlePaste);
|
||
console.log('전역 붙여넣기 이벤트 리스너 등록됨');
|
||
|
||
// 이미지 컨테이너 클릭 시 포커스
|
||
imageContainer.addEventListener('click', imageContainerClickHandler);
|
||
|
||
// 이미지 컨테이너에 포커스 가능하도록 설정
|
||
imageContainer.tabIndex = 0;
|
||
imageContainer.style.cursor = 'pointer';
|
||
imageContainer.title = '클릭 후 Ctrl+V로 이미지 붙여넣기';
|
||
|
||
// 포커스 시 오버레이 표시
|
||
imageContainer.addEventListener('focus', imageContainerFocusHandler);
|
||
|
||
// 포커스 해제 시 오버레이 숨김
|
||
imageContainer.addEventListener('blur', imageContainerBlurHandler);
|
||
|
||
// 마우스 오버 시 힌트 표시
|
||
imageContainer.addEventListener('mouseenter', function() {
|
||
if (pasteOverlay) {
|
||
pasteOverlay.style.display = 'block';
|
||
showPasteFeedback(false);
|
||
}
|
||
});
|
||
|
||
imageContainer.addEventListener('mouseleave', function() {
|
||
if (pasteOverlay) {
|
||
setTimeout(() => {
|
||
pasteOverlay.style.display = 'none';
|
||
}, 1000);
|
||
}
|
||
});
|
||
|
||
// 그리기 버튼 클릭 이벤트 리스너 추가
|
||
openBtn.addEventListener('click', openBtnClickHandler);
|
||
|
||
console.log('이미지 편집기 초기화 완료');
|
||
console.log('그리기 버튼 이벤트 리스너 등록됨:', openBtn);
|
||
};
|
||
|
||
</script>
|
||
|
||
|
||
<script>
|
||
var ajaxRequest_write =null;
|
||
// 전역 변수로 문자입력 모드 관리
|
||
let textMode = false;
|
||
let isEraser = false; // 이름을 isEraser 로 통일
|
||
|
||
// 1) calculateSum 수정: 연신율계산 후(calcAfterList[]) 처리 추가
|
||
function calculateSum() {
|
||
const inputList = $('input[name="inputList[]"]');
|
||
const bendingList = $('input[name="bendingrateList[]"]');
|
||
const calcAfterList = $('input[name="calcAfterList[]"]');
|
||
const sumList = $('input[name="sumList[]"]');
|
||
|
||
let accumulated = 0;
|
||
inputList.each(function(index) {
|
||
const inp = parseFloat(inputList.eq(index).val()) || 0;
|
||
const bend = parseFloat(bendingList.eq(index).val()) || 0;
|
||
// ────────────────────────────────
|
||
// 변경: 입력값에서 빼기 대신, bend(±1)를 더합니다.
|
||
const diff = inp + bend;
|
||
// ────────────────────────────────
|
||
|
||
calcAfterList.eq(index).val(diff);
|
||
accumulated += diff;
|
||
sumList.eq(index).val(accumulated);
|
||
});
|
||
|
||
$('#widthsum').val(accumulated);
|
||
}
|
||
|
||
// 2) addInitialColumn 수정
|
||
function addInitialColumn() {
|
||
const tableBody = $('#tableBody');
|
||
if (!tableBody.length) return;
|
||
|
||
// rowsData 배열에서 span의 className에서 'input-container'를 제거
|
||
const rowsData = [
|
||
{ label: '번호', type: 'span', className: 'form-control text-center' },
|
||
{ label: '입력', type: 'input', className: 'form-control yellowBold text-center', name: 'inputList[]', event: calculateSum },
|
||
{ label: '연신율', type: 'input', className: 'form-control text-center', name: 'bendingrateList[]', event: calculateSum },
|
||
{ label: '연신율계산 후', type: 'input', className: 'form-control text-center', name: 'calcAfterList[]', event: null, readonly: true },
|
||
{ label: '합계', type: 'input', className: 'form-control orangeBlackBold text-center', name: 'sumList[]', event: null },
|
||
{ label: '음영', type: 'checkbox', className: 'form-check-input', name: 'colorList[]' },
|
||
{ label: 'A각 표시', type: 'checkbox', className: 'form-check-input', name: 'AList[]' }
|
||
];
|
||
|
||
tableBody.empty();
|
||
|
||
rowsData.forEach((rowData, rowIndex) => {
|
||
const $row = $('<tr>');
|
||
// 첫 번째 셀: 라벨
|
||
$row.append($('<td>').addClass('lightgray').text(rowData.label));
|
||
|
||
// 두 번째 셀: 모든 행에 input-container 적용
|
||
const $cell = $('<td>').addClass('input-container');
|
||
|
||
if (rowData.type === 'span') {
|
||
// 번호용 span: 모든 요소와 동일한 폭 적용
|
||
$cell.append(
|
||
$('<span>')
|
||
.addClass(rowData.className)
|
||
.css({ display: 'inline-block', width: '40px', height: '24px', 'text-align': 'center', 'line-height': '24px' })
|
||
.text(rowIndex + 1)
|
||
);
|
||
} else {
|
||
// 모든 input 요소: 동일한 폭과 높이 적용
|
||
const $input = $('<input>')
|
||
.attr('type', rowData.type === 'checkbox' ? 'checkbox' : 'text')
|
||
.addClass(rowData.className)
|
||
.css(rowData.type === 'checkbox' ? { width: '16px', height: '16px' } : { width: '40px', height: '24px' })
|
||
.attr('name', rowData.name)
|
||
.prop('readonly', !!rowData.readonly)
|
||
.on(rowData.event ? 'input change' : [], rowData.event || (() => {}))
|
||
.attr('autocomplete', 'off');
|
||
$cell.append($input);
|
||
}
|
||
|
||
$row.append($cell);
|
||
tableBody.append($row);
|
||
wrapColumnsAndAttachRemove();
|
||
});
|
||
}
|
||
|
||
// 3) addColumn 수정: 새로 생긴 "연신율계산 후" 행에도 input 추가
|
||
function addColumn() {
|
||
const tableBody = $('#tableBody');
|
||
let $rows = tableBody.find('tr');
|
||
if ($rows.length === 0) {
|
||
addInitialColumn();
|
||
$rows = tableBody.find('tr');
|
||
}
|
||
|
||
// 새 칸 번호
|
||
const newIndex = $rows.first().find('.input-container span').length + 1;
|
||
|
||
$rows.each(function(rowIdx) {
|
||
const $cell = $(this).find('.input-container');
|
||
if (rowIdx === 0) {
|
||
// 번호 행: 동일한 폭 적용
|
||
$cell.append(
|
||
$('<span>')
|
||
.addClass('form-control text-center viewNoBtn')
|
||
.css({ width: '40px', height: '24px', 'text-align': 'center', 'line-height': '24px' })
|
||
.text(newIndex)
|
||
);
|
||
} else {
|
||
let $input = $('<input>')
|
||
.addClass('form-control')
|
||
.attr('autocomplete', 'off');
|
||
|
||
switch (rowIdx) {
|
||
case 1: // 입력
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('yellowBold text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'inputList[]')
|
||
.on('input change', calculateSum);
|
||
break;
|
||
case 2: // 연신율
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'bendingrateList[]')
|
||
.on('input change', calculateSum);
|
||
break;
|
||
case 3: // 연신율계산 후
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'calcAfterList[]')
|
||
.prop('readonly', true);
|
||
break;
|
||
case 4: // 합계
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('orangeBlackBold text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'sumList[]');
|
||
break;
|
||
case 5: // 음영
|
||
$input
|
||
.attr('type', 'checkbox')
|
||
.addClass('form-check-input')
|
||
.css({ width: '16px', height: '16px' })
|
||
.attr('name', 'colorList[]');
|
||
break;
|
||
case 6: // A각 표시
|
||
$input
|
||
.attr('type', 'checkbox')
|
||
.addClass('form-check-input')
|
||
.css({ width: '16px', height: '16px' })
|
||
.attr('name', 'AList[]');
|
||
break;
|
||
}
|
||
|
||
$cell.append($input);
|
||
}
|
||
wrapColumnsAndAttachRemove();
|
||
});
|
||
}
|
||
|
||
// 특정 위치에 열 삽입하는 함수
|
||
function insertColumnAt(index) {
|
||
const tableBody = $('#tableBody');
|
||
let $rows = tableBody.find('tr');
|
||
if ($rows.length === 0) {
|
||
addInitialColumn();
|
||
$rows = tableBody.find('tr');
|
||
}
|
||
|
||
$rows.each(function(rowIdx) {
|
||
const $cell = $(this).find('.input-container');
|
||
const $colCells = $cell.find('.col-cell');
|
||
|
||
if (rowIdx === 0) {
|
||
// 번호 행: span 요소 삽입
|
||
const $newSpan = $('<span>')
|
||
.addClass('form-control text-center viewNoBtn')
|
||
.css({ width: '40px', height: '24px', 'text-align': 'center', 'line-height': '24px' })
|
||
.text(index + 1);
|
||
|
||
// 해당 위치에 삽입
|
||
if (index < $colCells.length) {
|
||
$colCells.eq(index).before($('<div class="col-cell"></div>').append($newSpan));
|
||
} else {
|
||
$cell.append($('<div class="col-cell"></div>').append($newSpan));
|
||
}
|
||
} else {
|
||
let $input = $('<input>')
|
||
.addClass('form-control')
|
||
.attr('autocomplete', 'off');
|
||
|
||
switch (rowIdx) {
|
||
case 1: // 입력
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('yellowBold text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'inputList[]')
|
||
.on('input change', calculateSum);
|
||
break;
|
||
case 2: // 연신율
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'bendingrateList[]')
|
||
.on('input change', calculateSum);
|
||
break;
|
||
case 3: // 연신율계산 후
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'calcAfterList[]')
|
||
.prop('readonly', true);
|
||
break;
|
||
case 4: // 합계
|
||
$input
|
||
.attr('type', 'text')
|
||
.addClass('orangeBlackBold text-center')
|
||
.css({ width: '40px', height: '24px' })
|
||
.attr('name', 'sumList[]');
|
||
break;
|
||
case 5: // 음영
|
||
$input
|
||
.attr('type', 'checkbox')
|
||
.addClass('form-check-input')
|
||
.css({ width: '16px', height: '16px' })
|
||
.attr('name', 'colorList[]');
|
||
break;
|
||
case 6: // A각 표시
|
||
$input
|
||
.attr('type', 'checkbox')
|
||
.addClass('form-check-input')
|
||
.css({ width: '16px', height: '16px' })
|
||
.attr('name', 'AList[]');
|
||
break;
|
||
}
|
||
|
||
// 해당 위치에 삽입
|
||
if (index < $colCells.length) {
|
||
$colCells.eq(index).before($('<div class="col-cell"></div>').append($input));
|
||
} else {
|
||
$cell.append($('<div class="col-cell"></div>').append($input));
|
||
}
|
||
}
|
||
});
|
||
|
||
// 새로 추가된 열에 버튼들 추가
|
||
addButtonsToNewColumns();
|
||
|
||
// 번호 다시 매기기
|
||
$('#tableBody tr:first .col-cell span').each(function(i) {
|
||
$(this).text(i + 1);
|
||
});
|
||
|
||
// 합계 재계산
|
||
calculateSum();
|
||
alertToast('앞쪽에 열추가');
|
||
}
|
||
|
||
// 새로 추가된 열에 버튼들을 추가하는 함수
|
||
function addButtonsToNewColumns() {
|
||
$('#tableBody tr:first .col-cell').each(function(idx) {
|
||
const $colCell = $(this);
|
||
|
||
// 추가 버튼이 없으면 추가
|
||
if (!$colCell.find('.add-col').length) {
|
||
$colCell.append('<button type="button" class="add-col">+</button>');
|
||
}
|
||
|
||
// 삭제 버튼이 없으면 추가
|
||
if (!$colCell.find('.remove-col').length) {
|
||
$colCell.append('<button type="button" class="remove-col">–</button>');
|
||
}
|
||
});
|
||
}
|
||
|
||
function emptyColumn() {
|
||
const tableBody = $('#tableBody');
|
||
|
||
tableBody.find('input').each(function() {
|
||
const inputType = $(this).attr('type');
|
||
|
||
if (inputType === 'checkbox') {
|
||
$(this).prop('checked', false); // 체크박스 해제
|
||
} else {
|
||
$(this).val(''); // 텍스트 필드 비우기
|
||
}
|
||
});
|
||
}
|
||
|
||
function populateTableWithData() {
|
||
const inputList = <?php echo json_encode($inputList ?? []); ?>;
|
||
const bendingrateList = <?php echo json_encode($bendingrateList ?? []); ?>;
|
||
const sumList = <?php echo json_encode($sumList ?? []); ?>;
|
||
const colorList = <?php echo json_encode($colorList ?? []); ?>;
|
||
const AList = <?php echo json_encode($AList ?? []); ?>;
|
||
|
||
const tableBody = $('#tableBody');
|
||
|
||
if (!tableBody.length) return;
|
||
|
||
inputList.forEach(function (input, index) {
|
||
if (index === 0) {
|
||
addInitialColumn(); // 첫 번째 열은 기본 열 추가
|
||
} else {
|
||
addColumn(); // 이후 열 추가
|
||
}
|
||
});
|
||
|
||
// 데이터 채우기
|
||
inputList.forEach(function (input, index) {
|
||
// 입력 추가
|
||
tableBody.find('tr:nth-child(2) .input-container input').eq(index).val(input);
|
||
|
||
// 연신율 추가
|
||
tableBody.find('tr:nth-child(3) .input-container input').eq(index).val(bendingrateList[index] || '');
|
||
|
||
// 합계 추가
|
||
tableBody.find('tr:nth-child(5) .input-container input').eq(index).val(sumList[index] || '');
|
||
|
||
// 음영 체크박스 추가
|
||
tableBody.find('tr:nth-child(6) .input-container input').eq(index).prop('checked', colorList[index] || false);
|
||
|
||
// A각 표시 체크박스 추가
|
||
tableBody.find('tr:nth-child(7) .input-container input').eq(index).prop('checked', AList[index] || false);
|
||
});
|
||
}
|
||
|
||
function removeColumn() {
|
||
const tableBody = $('#tableBody');
|
||
const rows = tableBody.find('tr');
|
||
|
||
if (rows.length === 0) return;
|
||
|
||
rows.each(function (i) {
|
||
const inputContainer = $(this).find('.input-container');
|
||
|
||
if (i === 0) {
|
||
// 첫 번째 행(번호 행)은 span 요소를 제거합니다.
|
||
const spans = inputContainer.find('span');
|
||
if (spans.length > 1) {
|
||
inputContainer.find('span').last().remove();
|
||
}
|
||
} else {
|
||
// 나머지 행은 input 요소를 제거합니다.
|
||
const inputs = inputContainer.find('input');
|
||
if (inputs.length > 1) {
|
||
inputContainer.find('input').last().remove();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
$(document).ready(function () {
|
||
const mode = '<?php echo $mode; ?>';
|
||
|
||
$("#copyBtn").click(function(){
|
||
location.href = 'write_form.php?mode=copy&num=' + $("#num").val() + "&tablename=" + $("#tablename").val() ;
|
||
}); // end of function
|
||
|
||
if (mode === 'view' || mode === 'modify' || mode === 'copy' || mode === 'write' ) {
|
||
populateTableWithData();
|
||
} else {
|
||
addInitialColumn(); // 새로 입력하는 경우 기본 열 추가
|
||
}
|
||
|
||
if (mode === 'view') {
|
||
disableInputsForViewMode();
|
||
}
|
||
else
|
||
{ // view가 아닐때만
|
||
$('#emptyColumnBtn').on('click', emptyColumn);
|
||
$('#addColumnBtn').on('click', addColumn);
|
||
$('#removeColumnBtn').on('click', removeColumn);
|
||
}
|
||
var loader = $('#loadingOverlay');
|
||
if (loader.length) {
|
||
loader.hide();
|
||
}
|
||
|
||
});
|
||
|
||
function disableInputsForViewMode() {
|
||
$('input, textarea').prop('readonly', true);
|
||
$('select').prop('disabled', true);
|
||
$('input[type=file]').prop('readonly', false);
|
||
$('input[type=checkbox]').prop('disabled', true);
|
||
$('.viewNoBtn').prop('disabled', true); // 뷰모드에서는 금지
|
||
$('input[type=radio]').prop('disabled', true);
|
||
}
|
||
|
||
$(document).ready(function() {
|
||
// Log 파일보기
|
||
$("#showlogBtn").click( function() {
|
||
var num = '<?php echo $num; ?>'
|
||
// table 이름을 넣어야 함
|
||
var workitem = 'bending' ;
|
||
// 버튼 비활성화
|
||
var btn = $(this);
|
||
popupCenter("../Showlog.php?num=" + num + "&workitem=" + workitem , '로그기록', 500, 500);
|
||
btn.prop('disabled', false);
|
||
});
|
||
|
||
// 저장 버튼 클릭 이벤트
|
||
$('#saveBtn').off('click').on('click', function() {
|
||
// 이미지 편집이 완료되었지만 저장되지 않은 상태인지 확인
|
||
const targetImage = document.getElementById('targetImage');
|
||
const hasUnsavedImage = targetImage && targetImage.src && targetImage.src.startsWith('data:');
|
||
|
||
if (hasUnsavedImage) {
|
||
Swal.fire({
|
||
title: '이미지 편집 저장 확인',
|
||
text: '편집된 이미지가 저장되지 않았습니다. 저장하시겠습니까?',
|
||
icon: 'question',
|
||
showCancelButton: true,
|
||
confirmButtonColor: '#3085d6',
|
||
cancelButtonColor: '#6c757d',
|
||
confirmButtonText: '네, 저장하겠습니다',
|
||
cancelButtonText: '아니오, 그대로 저장'
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
// 이미지 저장 후 파일 저장
|
||
saveEditedImageAndFile();
|
||
} else {
|
||
// 이미지 저장 없이 파일 저장
|
||
saveFileOnly();
|
||
}
|
||
});
|
||
} else {
|
||
// 이미지 편집이 없으면 바로 파일 저장
|
||
saveFileOnly();
|
||
}
|
||
});
|
||
|
||
// 편집된 이미지 저장 후 파일 저장하는 함수
|
||
async function saveEditedImageAndFile() {
|
||
const targetImage = document.getElementById('targetImage');
|
||
if (targetImage && targetImage.src && targetImage.src.startsWith('data:')) {
|
||
try {
|
||
// 이미지 저장
|
||
await saveEditedImage(targetImage.src);
|
||
|
||
// 파일 저장 진행
|
||
saveFileOnly();
|
||
} catch (error) {
|
||
console.error('이미지 저장 실패:', error);
|
||
// 이미지 저장 실패해도 파일 저장은 진행
|
||
saveFileOnly();
|
||
}
|
||
} else {
|
||
// 저장할 이미지가 없으면 바로 파일 저장
|
||
saveFileOnly();
|
||
}
|
||
}
|
||
|
||
// 파일만 저장하는 함수
|
||
function saveFileOnly() {
|
||
const formData = new FormData($('#board_form')[0]);
|
||
|
||
// JSON 데이터 생성
|
||
const inputList = [];
|
||
$('input[name="inputList[]"]').each(function() {
|
||
inputList.push($(this).val());
|
||
});
|
||
const bendingrateList = [];
|
||
$('input[name="bendingrateList[]"]').each(function() {
|
||
bendingrateList.push($(this).val());
|
||
});
|
||
const sumList = [];
|
||
$('input[name="sumList[]"]').each(function() {
|
||
sumList.push($(this).val());
|
||
});
|
||
const colorList = [];
|
||
$('input[name="colorList[]"]').each(function() {
|
||
colorList.push($(this).is(':checked'));
|
||
});
|
||
const AList = [];
|
||
$('input[name="AList[]"]').each(function() {
|
||
AList.push($(this).is(':checked'));
|
||
});
|
||
const calcAfterList = [];
|
||
$('input[name="calcAfterList[]"]').each(function() {
|
||
calcAfterList.push($(this).val());
|
||
});
|
||
|
||
// JSON 데이터를 hidden input에 설정
|
||
formData.append('inputList', JSON.stringify(inputList));
|
||
formData.append('bendingrateList', JSON.stringify(bendingrateList));
|
||
formData.append('sumList', JSON.stringify(sumList));
|
||
formData.append('colorList', JSON.stringify(colorList));
|
||
formData.append('AList', JSON.stringify(AList));
|
||
formData.append('calcAfterList', JSON.stringify(calcAfterList));
|
||
|
||
// AJAX로 저장
|
||
$.ajax({
|
||
url: 'insert.php',
|
||
type: 'POST',
|
||
data: formData,
|
||
processData: false,
|
||
contentType: false,
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
console.log(' saveBtn 실행 저장 응답:', response);
|
||
try {
|
||
// 응답이 문자열인 경우 JSON 파싱 시도
|
||
let result;
|
||
if (typeof response === 'string') {
|
||
result = JSON.parse(response);
|
||
} else {
|
||
result = response;
|
||
}
|
||
|
||
if (result && result.success) {
|
||
Swal.fire({
|
||
title: '저장 완료',
|
||
text: '데이터가 성공적으로 저장되었습니다.',
|
||
icon: 'success',
|
||
confirmButtonText: '확인'
|
||
}).then((result) => {
|
||
// 저장 성공 시 페이지 새로고침 또는 다른 처리
|
||
if (result.num) {
|
||
location.href = 'write_form.php?mode=view&num=' + result.num + '&tablename=' + $('#tablename').val();
|
||
} else {
|
||
// num이 없으면 현재 페이지 새로고침
|
||
// location.reload();
|
||
}
|
||
});
|
||
} else {
|
||
Swal.fire({
|
||
title: '저장 실패',
|
||
text: '저장 중 오류가 발생했습니다: ' + (result ? result.message : '알 수 없는 오류'),
|
||
icon: 'error',
|
||
confirmButtonText: '확인'
|
||
});
|
||
}
|
||
} catch (e) {
|
||
console.error('저장 응답 파싱 오류:', e);
|
||
console.error('응답 내용:', response);
|
||
// JSON 파싱 실패해도 성공으로 처리 (insert.php가 성공 시 다른 형식 반환할 수 있음)
|
||
Swal.fire({
|
||
title: '저장 완료',
|
||
text: '데이터가 성공적으로 저장되었습니다.',
|
||
icon: 'success',
|
||
confirmButtonText: '확인'
|
||
}).then(() => {
|
||
location.reload();
|
||
});
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
Swal.fire({
|
||
title: '저장 오류',
|
||
text: '저장 중 오류가 발생했습니다.',
|
||
icon: 'error',
|
||
confirmButtonText: '확인'
|
||
});
|
||
console.error('저장 오류:', error);
|
||
}
|
||
});
|
||
}
|
||
|
||
// saveEditedImage 함수 (모듈 스크립트에서 정의된 함수 사용)
|
||
async function saveEditedImage(imageData) {
|
||
try {
|
||
console.log('이미지 저장 시작:', imageData.substring(0, 50) + '...');
|
||
|
||
// data URL을 Blob으로 변환
|
||
const response = await fetch(imageData);
|
||
const blob = await response.blob();
|
||
|
||
// FormData 구성
|
||
const formData = new FormData();
|
||
formData.append('upfile', blob, 'edited_image.png');
|
||
formData.append('num', '<?= $num ?>');
|
||
formData.append('mode', '<?= $mode ?>');
|
||
|
||
// 추가 데이터 (bending 테이블 관련)
|
||
formData.append('item_sep', '<?= $row['item_sep'] ?? '' ?>');
|
||
formData.append('model_UA', '<?= $row['model_UA'] ?? '' ?>');
|
||
formData.append('item_bending', '<?= $row['item_bending'] ?? '' ?>');
|
||
formData.append('itemName', '<?= $row['itemName'] ?? '' ?>');
|
||
formData.append('item_spec', '<?= $row['item_spec'] ?? '' ?>');
|
||
|
||
console.log('FormData 구성 완료, 서버 전송 중...');
|
||
|
||
const saveResponse = await fetch('save_edited_image.php', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
if (!saveResponse.ok) {
|
||
throw new Error('서버 저장 실패');
|
||
}
|
||
|
||
const result = await saveResponse.json();
|
||
if (result.success) {
|
||
console.log('이미지 저장 성공:', result);
|
||
|
||
// 성공 메시지 표시
|
||
if (typeof Toastify !== 'undefined') {
|
||
Toastify({
|
||
text: "이미지 저장 완료",
|
||
duration: 2000,
|
||
close: true,
|
||
gravity: "top",
|
||
position: "center",
|
||
backgroundColor: "#28a745",
|
||
}).showToast();
|
||
} else {
|
||
alert('이미지가 성공적으로 저장되었습니다.');
|
||
}
|
||
} else {
|
||
console.error('이미지 저장 실패:', result.message);
|
||
|
||
if (typeof Toastify !== 'undefined') {
|
||
Toastify({
|
||
text: "이미지 저장 실패: " + result.message,
|
||
duration: 3000,
|
||
close: true,
|
||
gravity: "top",
|
||
position: "center",
|
||
backgroundColor: "#dc3545",
|
||
}).showToast();
|
||
} else {
|
||
alert('이미지 저장 실패: ' + result.message);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('이미지 저장 오류:', error);
|
||
|
||
if (typeof Toastify !== 'undefined') {
|
||
Toastify({
|
||
text: "이미지 저장 중 오류 발생",
|
||
duration: 3000,
|
||
close: true,
|
||
gravity: "top",
|
||
position: "center",
|
||
backgroundColor: "#dc3545",
|
||
}).showToast();
|
||
} else {
|
||
alert('이미지 저장 중 오류가 발생했습니다.');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// 테이블을 다시 그린 직후, 또는 addColumn/addInitialColumn 끝에 호출하세요.
|
||
function wrapColumnsAndAttachRemove() {
|
||
$('#tableBody tr').each(function(rowIdx) {
|
||
const $cell = $(this).find('td.input-container');
|
||
// 이미 wrap 되어 있지 않은 직접 자식(input, span 등)만 골라서 감싸기
|
||
$cell
|
||
.contents()
|
||
.filter(function() {
|
||
return this.nodeType === 1 && !$(this).closest('.col-cell').length;
|
||
})
|
||
.each(function() {
|
||
$(this).wrap('<div class="col-cell"></div>');
|
||
});
|
||
});
|
||
|
||
// 모든 열에 추가/삭제 버튼 붙이기
|
||
addButtonsToNewColumns();
|
||
}
|
||
|
||
// 열추가동작
|
||
$(document).on('click', '.add-col', function() {
|
||
const $wrapper = $(this).closest('.col-cell');
|
||
const idx = $wrapper.index(); // 몇 번째 열인지
|
||
|
||
// mode가 view가 아닐때 해당 열(인덱스) 위치에 새 열 삽입
|
||
if ($('#mode').val() !== 'view') {
|
||
insertColumnAt(idx);
|
||
}
|
||
});
|
||
|
||
// 열삭제동작
|
||
$(document).on('click', '.remove-col', function() {
|
||
const $wrapper = $(this).closest('.col-cell');
|
||
const idx = $wrapper.index(); // 몇 번째 열인지
|
||
|
||
// mode가 view가 아닐때 해당 열(인덱스) 전체를 지움
|
||
if ($('#mode').val() !== 'view') {
|
||
$('#tableBody .input-container').each(function() {
|
||
$(this).find('.col-cell').eq(idx).remove();
|
||
});
|
||
|
||
// 번호 다시 매기기
|
||
$('#tableBody tr:first .col-cell span').each(function(i) {
|
||
$(this).text(i + 1);
|
||
});
|
||
|
||
// 합계 재계산
|
||
calculateSum();
|
||
alertToast('해당열 열삭제');
|
||
}
|
||
});
|
||
|
||
</script>
|
||
|
||
<script>
|
||
// 이미지 편집 관련 전역 변수 선언
|
||
$(document).ready(function() {
|
||
// DOM 요소 확인 및 이벤트 리스너 등록
|
||
setTimeout(() => {
|
||
const openBtn = document.getElementById('openEditorBtn');
|
||
const targetImg = document.getElementById('targetImage');
|
||
const imageContainer = document.querySelector('.image-container');
|
||
const pasteOverlay = document.querySelector('.paste-overlay');
|
||
|
||
console.log('DOM 요소 확인:', {
|
||
openBtn: !!openBtn,
|
||
targetImg: !!targetImg,
|
||
imageContainer: !!imageContainer,
|
||
pasteOverlay: !!pasteOverlay
|
||
});
|
||
|
||
if (openBtn) {
|
||
console.log('그리기 버튼 찾음:', openBtn);
|
||
console.log('그리기 버튼 텍스트:', openBtn.textContent);
|
||
console.log('그리기 버튼 클래스:', openBtn.className);
|
||
console.log('그리기 버튼 스타일:', openBtn.style.display);
|
||
|
||
// 그리기 버튼에 직접 이벤트 리스너 추가
|
||
openBtn.onclick = function() {
|
||
console.log('그리기 버튼 클릭됨 (직접 이벤트)');
|
||
|
||
const globalOpenImageEditor = window.openImageEditor;
|
||
console.log('전역 openImageEditor 확인:', typeof globalOpenImageEditor);
|
||
|
||
if (typeof globalOpenImageEditor !== 'function') {
|
||
console.error('전역 openImageEditor가 함수가 아닙니다!');
|
||
return;
|
||
}
|
||
|
||
// 현재 이미지 요소 찾기 (기존 targetImg 또는 새로 생성된 이미지)
|
||
let currentImg = targetImg || imageContainer.querySelector('#targetImage');
|
||
let src = currentImg ? currentImg.src : '';
|
||
console.log('편집할 이미지 소스:', src);
|
||
|
||
// 이미지가 없거나 placeholder인 경우 빈 문자열로 설정
|
||
if (!src || src.includes('placeholder') || src === 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2Y5ZjlmOSIvPjwvc3ZnPg==') {
|
||
console.log('이미지가 없거나 placeholder, 새로운 빈 캔버스로 시작');
|
||
src = '';
|
||
}
|
||
|
||
try {
|
||
const result = globalOpenImageEditor(src);
|
||
console.log('openImageEditor 호출 결과:', result);
|
||
|
||
if (result && typeof result.then === 'function') {
|
||
result.then(newUrl => {
|
||
console.log('편집 완료, 새로운 이미지 URL:', newUrl);
|
||
|
||
// 현재 이미지 요소 찾기 (기존 targetImg 또는 새로 생성된 이미지)
|
||
let imgElement = targetImg || imageContainer.querySelector('#targetImage');
|
||
if (!imgElement) {
|
||
console.log('이미지 요소가 없어서 새로 생성');
|
||
imgElement = document.createElement('img');
|
||
imgElement.id = 'targetImage';
|
||
imgElement.className = 'img-fluid';
|
||
imgElement.style.maxWidth = '100%';
|
||
imgElement.style.height = 'auto';
|
||
imageContainer.appendChild(imgElement);
|
||
}
|
||
|
||
imgElement.src = newUrl;
|
||
|
||
// "이미지가 없습니다" 메시지 숨기기
|
||
const noImageMsg = imageContainer.querySelector('p.text-muted');
|
||
if (noImageMsg && noImageMsg.textContent.includes('이미지가 없습니다')) {
|
||
noImageMsg.style.display = 'none';
|
||
console.log('"이미지가 없습니다" 메시지 숨김');
|
||
}
|
||
}).catch(err => {
|
||
console.log('편집 취소:', err.message);
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('openImageEditor 호출 중 오류:', error);
|
||
}
|
||
};
|
||
|
||
console.log('그리기 버튼 이벤트 리스너 등록 완료');
|
||
} else {
|
||
console.warn('그리기 버튼을 찾을 수 없습니다!');
|
||
}
|
||
if (targetImg) console.log('타겟 이미지 찾음:', targetImg.src);
|
||
if (imageContainer) console.log('이미지 컨테이너 찾음');
|
||
if (pasteOverlay) console.log('붙여넣기 오버레이 찾음');
|
||
|
||
// 붙여넣기 기능 초기화 (imageContainer만 있으면 동작)
|
||
if (imageContainer) {
|
||
console.log('붙여넣기 기능 초기화 시작...');
|
||
|
||
// 붙여넣기 이벤트 처리 함수
|
||
function handlePaste(event) {
|
||
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||
|
||
for (let item of items) {
|
||
if (item.type.indexOf('image') !== -1) {
|
||
const blob = item.getAsFile();
|
||
const reader = new FileReader();
|
||
|
||
reader.onload = function(e) {
|
||
// targetImg가 있으면 업데이트, 없으면 새로 생성
|
||
let imgElement = targetImg;
|
||
if (!imgElement) {
|
||
console.log('targetImg가 없어서 새로 생성');
|
||
imgElement = document.createElement('img');
|
||
imgElement.id = 'targetImage';
|
||
imgElement.className = 'img-fluid';
|
||
imgElement.style.maxWidth = '100%';
|
||
imgElement.style.height = 'auto';
|
||
imageContainer.appendChild(imgElement);
|
||
}
|
||
|
||
imgElement.src = e.target.result;
|
||
console.log('이미지가 붙여넣기되었습니다:', e.target.result);
|
||
|
||
// "이미지가 없습니다" 메시지 숨기기
|
||
const noImageMsg = imageContainer.querySelector('p.text-muted');
|
||
if (noImageMsg && noImageMsg.textContent.includes('이미지가 없습니다')) {
|
||
noImageMsg.style.display = 'none';
|
||
console.log('"이미지가 없습니다" 메시지 숨김');
|
||
}
|
||
|
||
// 붙여넣기 성공 피드백
|
||
showPasteFeedback(true);
|
||
};
|
||
|
||
reader.readAsDataURL(blob);
|
||
event.preventDefault();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 붙여넣기 피드백 표시
|
||
function showPasteFeedback(success = false) {
|
||
if (pasteOverlay) {
|
||
if (success) {
|
||
pasteOverlay.innerHTML = '<i class="bi bi-check-circle"></i><span>이미지가 붙여넣기되었습니다!</span>';
|
||
pasteOverlay.style.background = 'rgba(40, 167, 69, 0.8)';
|
||
} else {
|
||
pasteOverlay.innerHTML = '<i class="bi bi-clipboard-plus"></i><span>Ctrl+V로 이미지 붙여넣기</span>';
|
||
pasteOverlay.style.background = 'rgba(0, 123, 255, 0.8)';
|
||
}
|
||
|
||
pasteOverlay.classList.add('show');
|
||
setTimeout(() => {
|
||
pasteOverlay.classList.remove('show');
|
||
}, 2000);
|
||
} else {
|
||
// pasteOverlay가 없으면 간단한 콘솔 메시지
|
||
if (success) {
|
||
console.log('이미지가 성공적으로 붙여넣기되었습니다!');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 이벤트 핸들러 함수들
|
||
function imageContainerClickHandler() {
|
||
imageContainer.focus();
|
||
}
|
||
|
||
function imageContainerFocusHandler() {
|
||
showPasteFeedback();
|
||
}
|
||
|
||
function imageContainerBlurHandler() {
|
||
if (pasteOverlay) {
|
||
pasteOverlay.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
// 기존 이벤트 리스너 제거 (중복 방지)
|
||
document.removeEventListener('paste', handlePaste);
|
||
imageContainer.removeEventListener('click', imageContainerClickHandler);
|
||
if (pasteOverlay) {
|
||
imageContainer.removeEventListener('focus', imageContainerFocusHandler);
|
||
imageContainer.removeEventListener('blur', imageContainerBlurHandler);
|
||
}
|
||
|
||
// 붙여넣기 이벤트 리스너 추가
|
||
document.addEventListener('paste', handlePaste);
|
||
|
||
// 이미지 컨테이너 클릭 시 포커스
|
||
imageContainer.addEventListener('click', imageContainerClickHandler);
|
||
|
||
// 이미지 컨테이너에 포커스 가능하도록 설정
|
||
imageContainer.tabIndex = 0;
|
||
imageContainer.title = '클릭 후 Ctrl+V로 이미지 붙여넣기';
|
||
|
||
// 포커스 시 오버레이 표시 (pasteOverlay가 있을 때만)
|
||
if (pasteOverlay) {
|
||
imageContainer.addEventListener('focus', imageContainerFocusHandler);
|
||
imageContainer.addEventListener('blur', imageContainerBlurHandler);
|
||
}
|
||
|
||
console.log('붙여넣기 기능 초기화 완료');
|
||
} else {
|
||
console.warn('붙여넣기 기능 초기화 실패: 필요한 DOM 요소를 찾을 수 없습니다.');
|
||
}
|
||
}, 100);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|