오류수정, 암호화 추가
This commit is contained in:
@@ -133,7 +133,7 @@ exports.uploadEventFile = (req, res) => {
|
|||||||
// 업로드된 파일 파싱 및 미리보기 데이터 제공
|
// 업로드된 파일 파싱 및 미리보기 데이터 제공
|
||||||
exports.parseEventFile = async (req, res) => {
|
exports.parseEventFile = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { filename, clubId } = req.body;
|
const { filename, clubId, sheetName } = req.body;
|
||||||
|
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
return res.status(400).json({ message: '파일명이 필요합니다.' });
|
return res.status(400).json({ message: '파일명이 필요합니다.' });
|
||||||
@@ -196,7 +196,7 @@ exports.parseEventFile = async (req, res) => {
|
|||||||
// 파일 형식에 따른 처리
|
// 파일 형식에 따른 처리
|
||||||
if (fileExt === '.xlsx' || fileExt === '.xls') {
|
if (fileExt === '.xlsx' || fileExt === '.xls') {
|
||||||
// Excel 파일 처리
|
// Excel 파일 처리
|
||||||
parsedData = await parseExcelFile(filePath, memberMap);
|
parsedData = await parseExcelFile(filePath, memberMap, sheetName);
|
||||||
} else if (fileExt === '.csv') {
|
} else if (fileExt === '.csv') {
|
||||||
// CSV 파일 처리
|
// CSV 파일 처리
|
||||||
parsedData = await parseCsvFile(filePath, memberMap);
|
parsedData = await parseCsvFile(filePath, memberMap);
|
||||||
@@ -227,9 +227,14 @@ exports.parseEventFile = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Excel 파일 파싱 함수
|
// Excel 파일 파싱 함수
|
||||||
async function parseExcelFile(filePath, memberMap) {
|
async function parseExcelFile(filePath, memberMap, requestedSheetName = null) {
|
||||||
const workbook = xlsx.readFile(filePath, { type: 'binary', cellDates: true, codepage: 949 });
|
const workbook = xlsx.readFile(filePath, { type: 'binary', cellDates: true, codepage: 949 });
|
||||||
const sheetName = workbook.SheetNames[0];
|
|
||||||
|
// 요청된 시트명이 있고 해당 시트가 존재하면 그것을 사용, 아니면 첫 번째 시트 사용
|
||||||
|
const sheetName = (requestedSheetName && workbook.SheetNames.includes(requestedSheetName))
|
||||||
|
? requestedSheetName
|
||||||
|
: workbook.SheetNames[0];
|
||||||
|
|
||||||
const worksheet = workbook.Sheets[sheetName];
|
const worksheet = workbook.Sheets[sheetName];
|
||||||
const jsonData = xlsx.utils.sheet_to_json(worksheet, { header: 1, defval: '' });
|
const jsonData = xlsx.utils.sheet_to_json(worksheet, { header: 1, defval: '' });
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const { DataTypes } = require('sequelize');
|
|||||||
const { sequelize } = require('../config/database');
|
const { sequelize } = require('../config/database');
|
||||||
const User = require('./User');
|
const User = require('./User');
|
||||||
const Club = require('./Club');
|
const Club = require('./Club');
|
||||||
|
const { encrypt, decrypt } = require('../utils/encryption');
|
||||||
|
|
||||||
const Member = sequelize.define('Member', {
|
const Member = sequelize.define('Member', {
|
||||||
id: {
|
id: {
|
||||||
@@ -53,12 +54,26 @@ const Member = sequelize.define('Member', {
|
|||||||
phone: {
|
phone: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
comment: '전화번호'
|
comment: '전화번호 (암호화됨)',
|
||||||
|
get() {
|
||||||
|
const rawValue = this.getDataValue('phone');
|
||||||
|
return rawValue ? decrypt(rawValue) : null;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.setDataValue('phone', value ? encrypt(value) : null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
comment: '이메일'
|
comment: '이메일 (암호화됨)',
|
||||||
|
get() {
|
||||||
|
const rawValue = this.getDataValue('email');
|
||||||
|
return rawValue ? decrypt(rawValue) : null;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.setDataValue('email', value ? encrypt(value) : null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
joinDate: {
|
joinDate: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
// 환경 변수에서 암호화 키 가져오기
|
||||||
|
const RAW_ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || '안전한_암호화_키_설정';
|
||||||
|
// 키를 정확히 32바이트(256비트)로 변환
|
||||||
|
let ENCRYPTION_KEY;
|
||||||
|
try {
|
||||||
|
// 동기적으로 키 생성 (초기화 시 한 번만 실행됨)
|
||||||
|
ENCRYPTION_KEY = crypto.createHash('sha256').update(RAW_ENCRYPTION_KEY).digest();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('암호화 키 생성 오류:', error);
|
||||||
|
throw new Error('암호화 키 생성 실패');
|
||||||
|
}
|
||||||
|
|
||||||
|
const IV_LENGTH = 16; // AES 블록 크기
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문자열을 암호화하는 함수
|
||||||
|
* @param {string} text - 암호화할 텍스트
|
||||||
|
* @returns {string} - 암호화된 텍스트 (base64 인코딩)
|
||||||
|
*/
|
||||||
|
function encrypt(text) {
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
const iv = crypto.randomBytes(IV_LENGTH);
|
||||||
|
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
|
||||||
|
|
||||||
|
let encrypted = cipher.update(text);
|
||||||
|
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
||||||
|
|
||||||
|
// IV와 암호화된 데이터를 함께 저장 (복호화 시 필요)
|
||||||
|
return iv.toString('hex') + ':' + encrypted.toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 암호화된 문자열을 복호화하는 함수
|
||||||
|
* @param {string} text - 복호화할 암호화된 텍스트
|
||||||
|
* @returns {string} - 복호화된 원본 텍스트
|
||||||
|
*/
|
||||||
|
function decrypt(text) {
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
const textParts = text.split(':');
|
||||||
|
if (textParts.length !== 2) return null;
|
||||||
|
|
||||||
|
const iv = Buffer.from(textParts[0], 'hex');
|
||||||
|
const encryptedText = Buffer.from(textParts[1], 'hex');
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
|
||||||
|
|
||||||
|
let decrypted = decipher.update(encryptedText);
|
||||||
|
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
||||||
|
|
||||||
|
return decrypted.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
encrypt,
|
||||||
|
decrypt
|
||||||
|
};
|
||||||
Generated
+104
@@ -34,6 +34,7 @@
|
|||||||
"vue-chartjs": "^5.3.2",
|
"vue-chartjs": "^5.3.2",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
"yup": "^1.3.3"
|
"yup": "^1.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1828,6 +1829,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/adler-32": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@@ -1937,6 +1947,19 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/cfb": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"adler-32": "~1.3.0",
|
||||||
|
"crc-32": "~1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chart.js": {
|
"node_modules/chart.js": {
|
||||||
"version": "4.4.9",
|
"version": "4.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
|
||||||
@@ -1949,6 +1972,15 @@
|
|||||||
"pnpm": ">=8"
|
"pnpm": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codepage": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -1983,6 +2015,18 @@
|
|||||||
"url": "https://github.com/sponsors/mesqueeb"
|
"url": "https://github.com/sponsors/mesqueeb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crc-32": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"crc32": "bin/crc32.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -2339,6 +2383,15 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/frac": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "11.3.0",
|
"version": "11.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
|
||||||
@@ -3201,6 +3254,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ssf": {
|
||||||
|
"version": "0.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||||
|
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"frac": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strip-final-newline": {
|
"node_modules/strip-final-newline": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
|
||||||
@@ -3616,6 +3681,24 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wmf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/word": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.17.1",
|
"version": "8.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
@@ -3637,6 +3720,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xlsx": {
|
||||||
|
"version": "0.18.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||||
|
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"adler-32": "~1.3.0",
|
||||||
|
"cfb": "~1.2.1",
|
||||||
|
"codepage": "~1.15.0",
|
||||||
|
"crc-32": "~1.2.1",
|
||||||
|
"ssf": "~0.11.2",
|
||||||
|
"wmf": "~1.0.1",
|
||||||
|
"word": "~0.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"xlsx": "bin/xlsx.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xmlhttprequest-ssl": {
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"vue-chartjs": "^5.3.2",
|
"vue-chartjs": "^5.3.2",
|
||||||
"vue-router": "^4.5.0",
|
"vue-router": "^4.5.0",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
"yup": "^1.3.3"
|
"yup": "^1.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
<div class="file-upload-wrapper">
|
<div class="file-upload-wrapper">
|
||||||
<FileUpload
|
<FileUpload
|
||||||
|
ref="fileUploadRef"
|
||||||
name="file"
|
name="file"
|
||||||
:url="uploadUrl"
|
:url="uploadUrl"
|
||||||
@upload="onUpload"
|
@upload="onUpload"
|
||||||
@@ -68,7 +69,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</FileUpload>
|
</FileUpload>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 시트 선택 UI -->
|
||||||
|
<div v-if="sheetNames.length > 0" class="mt-3">
|
||||||
|
<label for="sheet-select" class="field-label">엑셀 시트 선택</label>
|
||||||
|
<Select id="sheet-select" v-model="selectedSheetName" :options="sheetNames" placeholder="시트 선택" class="p-inputtext" />
|
||||||
|
<Button label="시트 데이터 불러오기" class="ml-2" @click="parseFile" :disabled="!selectedSheetName || loading" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="uploadError" class="error-message">
|
<div v-if="uploadError" class="error-message">
|
||||||
<i class="pi pi-exclamation-triangle"></i>
|
<i class="pi pi-exclamation-triangle"></i>
|
||||||
{{ uploadError }}
|
{{ uploadError }}
|
||||||
@@ -498,7 +506,7 @@
|
|||||||
|
|
||||||
<div class="field col-6 mb-3">
|
<div class="field col-6 mb-3">
|
||||||
<label for="newMemberType" class="mb-1">회원 유형</label>
|
<label for="newMemberType" class="mb-1">회원 유형</label>
|
||||||
<Select id="newMemberType" v-model="newMember.memberType" :options="MEMBER_TYPE_OPTIONS" optionLabel="name" optionValue="code" placeholder="회원 유형 선택" class="w-full" :disabled="newMemberMode === 'guest'" />
|
<Select id="newMemberType" v-model="newMember.memberType" :options="MEMBER_TYPE_OPTIONS" optionLabel="name" optionValue="value" placeholder="회원 유형 선택" class="w-full" :disabled="newMemberMode === 'guest'" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field col-6 mb-3">
|
<div class="field col-6 mb-3">
|
||||||
@@ -518,7 +526,7 @@
|
|||||||
|
|
||||||
<div class="field col-6 mb-3">
|
<div class="field col-6 mb-3">
|
||||||
<label for="newMemberStatus" class="mb-1">상태</label>
|
<label for="newMemberStatus" class="mb-1">상태</label>
|
||||||
<Select id="newMemberStatus" v-model="newMember.status" :options="STATUS_OPTIONS" optionLabel="label" optionValue="value" placeholder="상태 선택" class="w-full" />
|
<Select id="newMemberStatus" v-model="newMember.status" :options="STATUS_OPTIONS" optionLabel="name" optionValue="value" placeholder="상태 선택" class="w-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -548,6 +556,7 @@ import dayjs from 'dayjs';
|
|||||||
import { useClubStore } from '@/stores/clubStore';
|
import { useClubStore } from '@/stores/clubStore';
|
||||||
import eventService from '@/services/eventService';
|
import eventService from '@/services/eventService';
|
||||||
import clubService from '@/services/clubService';
|
import clubService from '@/services/clubService';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
|
||||||
// 기존 이벤트 선택 관련 상태
|
// 기존 이벤트 선택 관련 상태
|
||||||
const selectedEventId = ref(null);
|
const selectedEventId = ref(null);
|
||||||
@@ -591,9 +600,7 @@ const loadSelectedEvent = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
// onMounted 훅은 아래에서 한 번만 사용
|
||||||
loadEventOptions();
|
|
||||||
});
|
|
||||||
|
|
||||||
// PrimeVue 컴포넌트 임포트
|
// PrimeVue 컴포넌트 임포트
|
||||||
|
|
||||||
@@ -626,9 +633,13 @@ const steps = ref([
|
|||||||
{ label: '최종 확인' }
|
{ label: '최종 확인' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// FileUpload 컴포넌트 참조
|
||||||
|
const fileUploadRef = ref(null);
|
||||||
|
|
||||||
const uploadedFile = ref(null);
|
const uploadedFile = ref(null);
|
||||||
const uploadError = ref('');
|
const uploadError = ref('');
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const uploading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const submitted = ref(false);
|
const submitted = ref(false);
|
||||||
|
|
||||||
@@ -638,6 +649,10 @@ const parsedData = ref({
|
|||||||
gameCount: 0
|
gameCount: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 엑셀 시트 관련 상태
|
||||||
|
const sheetNames = ref([]);
|
||||||
|
const selectedSheetName = ref('');
|
||||||
|
|
||||||
// 이벤트 데이터
|
// 이벤트 데이터
|
||||||
const eventData = ref({
|
const eventData = ref({
|
||||||
title: '',
|
title: '',
|
||||||
@@ -684,8 +699,9 @@ const uploadUrl = computed(() => {
|
|||||||
return `${import.meta.env.VITE_API_URL}${eventService.getFileUploadUrl(props.clubId)}`;
|
return `${import.meta.env.VITE_API_URL}${eventService.getFileUploadUrl(props.clubId)}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 클럽 회원 목록 로드
|
// 컴포넌트 마운트 시 초기화 작업 수행
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
loadEventOptions();
|
||||||
await loadClubMembers();
|
await loadClubMembers();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -716,9 +732,6 @@ const loadClubMembers = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 업로드 상태 변수 추가
|
|
||||||
const uploading = ref(false);
|
|
||||||
|
|
||||||
// 이미지 파일 여부 체크
|
// 이미지 파일 여부 체크
|
||||||
const isImageFile = (file) => {
|
const isImageFile = (file) => {
|
||||||
const ext = file.name.split('.').pop().toLowerCase();
|
const ext = file.name.split('.').pop().toLowerCase();
|
||||||
@@ -760,21 +773,31 @@ const uploadFile = async (event) => {
|
|||||||
uploadError.value = '업로드할 파일을 선택해주세요.';
|
uploadError.value = '업로드할 파일을 선택해주세요.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 프론트에서 시트명 추출
|
||||||
|
const file = event.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const data = new Uint8Array(e.target.result);
|
||||||
|
const workbook = XLSX.read(data, { type: 'array' });
|
||||||
|
sheetNames.value = workbook.SheetNames;
|
||||||
|
// 기본값: 첫 번째 시트 선택
|
||||||
|
selectedSheetName.value = workbook.SheetNames[0] || '';
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', event.files[0]);
|
formData.append('file', file);
|
||||||
formData.append('clubId', props.clubId);
|
formData.append('clubId', props.clubId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
uploading.value = true;
|
uploading.value = true;
|
||||||
const data = await eventService.uploadFile(event.files[0], props.clubId);
|
const data = await eventService.uploadFile(file, props.clubId);
|
||||||
uploadedFile.value = data.file;
|
uploadedFile.value = data.file;
|
||||||
uploadError.value = '';
|
uploadError.value = '';
|
||||||
|
// 파일 파싱은 시트 선택 후 별도로 진행
|
||||||
// 파일 파싱 요청
|
// parseFile();
|
||||||
parseFile();
|
|
||||||
|
|
||||||
// 업로드 완료 후 파일 목록 초기화
|
// 업로드 완료 후 파일 목록 초기화
|
||||||
if (event.clear) {
|
if (event.clear) {
|
||||||
event.clear();
|
event.clear();
|
||||||
@@ -808,40 +831,41 @@ const parseFile = async () => {
|
|||||||
if (!uploadedFile.value) {
|
if (!uploadedFile.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!selectedSheetName.value) {
|
||||||
|
uploadError.value = '시트를 선택해주세요.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await eventService.parseEventFile({
|
const response = await eventService.parseEventFile({
|
||||||
filename: uploadedFile.value.filename,
|
filename: uploadedFile.value.filename,
|
||||||
clubId: props.clubId
|
clubId: props.clubId,
|
||||||
|
sheetName: selectedSheetName.value
|
||||||
});
|
});
|
||||||
|
|
||||||
parsedData.value = response.data;
|
parsedData.value = response.data;
|
||||||
eventData.value.gameCount = parsedData.value.gameCount || 3;
|
eventData.value.gameCount = parsedData.value.gameCount || 3;
|
||||||
|
|
||||||
// 추론된 이벤트 정보가 있는 경우 이를 활용
|
// 추론된 이벤트 정보가 있는 경우 이를 활용
|
||||||
if (parsedData.value.inferredEventInfo) {
|
if (parsedData.value.inferredEventInfo) {
|
||||||
const inferredInfo = parsedData.value.inferredEventInfo;
|
const inferredInfo = parsedData.value.inferredEventInfo;
|
||||||
|
|
||||||
// 제목이 추론되었다면 적용
|
|
||||||
if (inferredInfo.title) {
|
if (inferredInfo.title) {
|
||||||
eventData.value.title = inferredInfo.title;
|
eventData.value.title = inferredInfo.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 시작일이 추론되었다면 적용
|
|
||||||
if (inferredInfo.startDate) {
|
if (inferredInfo.startDate) {
|
||||||
eventData.value.startDate = new Date(inferredInfo.startDate);
|
eventData.value.startDate = new Date(inferredInfo.startDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 장소가 추론되었다면 적용
|
|
||||||
if (inferredInfo.location) {
|
if (inferredInfo.location) {
|
||||||
eventData.value.location = inferredInfo.location;
|
eventData.value.location = inferredInfo.location;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다음 단계로 이동
|
// 다음 단계로 이동
|
||||||
activeStep.value = 1;
|
activeStep.value = 1;
|
||||||
|
|
||||||
|
// 파일 파싱이 완료되면 파일 업로드 컴포넌트 초기화
|
||||||
|
if (fileUploadRef.value && fileUploadRef.value.clear) {
|
||||||
|
fileUploadRef.value.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 업로드 파일 정보는 유지 (파일명은 백엔드에서 사용하기 때문)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('파일 파싱 오류:', error);
|
console.error('파일 파싱 오류:', error);
|
||||||
uploadError.value = error.message || '파일 파싱 중 오류가 발생했습니다.';
|
uploadError.value = error.message || '파일 파싱 중 오류가 발생했습니다.';
|
||||||
@@ -1231,6 +1255,8 @@ watch(() => props.visible, (newValue) => {
|
|||||||
status: '활성'
|
status: '활성'
|
||||||
};
|
};
|
||||||
submitted.value = false;
|
submitted.value = false;
|
||||||
|
sheetNames.value = [];
|
||||||
|
selectedSheetName.value = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user