Compare commits
41 Commits
21eac9a417
...
Develope
| Author | SHA1 | Date | |
|---|---|---|---|
| f4c4c6906e | |||
| 0cf0f8c49b | |||
| f8a1328a03 | |||
| f677fdb162 | |||
| abd4c24c83 | |||
| 7c390aebdf | |||
| 7f0d0f4a87 | |||
| 8b20758404 | |||
| 0741d5846b | |||
| 790e55f6fa | |||
| 5dfaa68e0e | |||
| e936250391 | |||
| 017b7edc24 | |||
| 4e32adbe44 | |||
| 44601131a2 | |||
| 6fb1c9fb2e | |||
| e5f4f423f4 | |||
| 77a1690e08 | |||
| 8c99ba1147 | |||
| fcb5285788 | |||
| 28ad63ffd9 | |||
| 73c78cd9e7 | |||
| 80563bfb7b | |||
| 42356d0f88 | |||
| 9618374942 | |||
| 1dca536ab0 | |||
| 9614266453 | |||
| dd2bbf46a6 | |||
| 97effcc442 | |||
| 4131f03e42 | |||
| 4632b2cdc8 | |||
| 947a791d87 | |||
| 89cd1357f0 | |||
| d3ae86b18d | |||
| 662aa4902d | |||
| 170d22273f | |||
| 0c34bd36a1 | |||
| 493ff96673 | |||
| c6973a3618 | |||
| 525ae4d2b5 | |||
| 85e0c18115 |
@@ -0,0 +1,2 @@
|
|||||||
|
# .env
|
||||||
|
DATA_DIR=/srv/dev-disk-by-uuid-0c76cdc9-484e-45a6-9283-49e75e12eece/dockerApp/ReDiveClanBattleRecordBackend
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# 使用 Kotlin + JDK 的輕量化映像
|
||||||
|
FROM gradle:9.5.1-jdk25 AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV GRADLE_OPTS="-Xmx1536m -XX:MaxMetaspaceSize=512m"
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=1536"
|
||||||
|
|
||||||
|
RUN gradle clean shadowJar --no-daemon
|
||||||
|
|
||||||
|
# 運行階段
|
||||||
|
FROM eclipse-temurin:25-jdk-ubi10-minimal
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
CMD ["java", "-jar", "app.jar", "club.sakunadaisuki.pcrediveclanrecordbackend.mainKt"]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# PcReDiveClanRecordBackend2
|
||||||
|
|
||||||
|
This project was created using the [Ktor Project Generator](https://start.ktor.io).
|
||||||
|
|
||||||
|
Here are some useful links to get you started:
|
||||||
|
|
||||||
|
* [Ktor Documentation](https://ktor.io/docs/home.html)
|
||||||
|
* [Ktor GitHub page](https://github.com/ktorio/ktor)
|
||||||
|
* [Ktor Slack chat](https://app.slack.com/client/T09229ZC6/C0A974TJ9). [Request an invite](https://surveys.jetbrains.com/s3/kotlin-slack-sign-up).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Here's a list of features included in this project:
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
|---------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
||||||
|
| [CORS](https://start.ktor.io/p/io.ktor/server-cors) | Enables Cross-Origin Resource Sharing (CORS) |
|
||||||
|
| [Swagger](https://start.ktor.io/p/io.ktor/server-swagger) | Serves Swagger UI for your project |
|
||||||
|
| [Content Negotiation](https://start.ktor.io/p/io.ktor/server-content-negotiation) | Provides automatic content conversion according to Content-Type and Accept headers |
|
||||||
|
| [kotlinx.serialization](https://start.ktor.io/p/io.ktor/server-kotlinx-serialization) | Handles JSON serialization using kotlinx.serialization library |
|
||||||
|
| [MongoDB](https://start.ktor.io/p/org.jetbrains/server-mongodb) | Adds MongoDB database support |
|
||||||
|
|
||||||
|
## Building & Running
|
||||||
|
|
||||||
|
To build or run the project, use one of the following tasks:
|
||||||
|
|
||||||
|
| Task | Description |
|
||||||
|
|-------------------|-------------------|
|
||||||
|
| `./gradlew test` | Run the tests |
|
||||||
|
| `./gradlew build` | Build the project |
|
||||||
|
| `./gradlew run` | Run the server |
|
||||||
|
|
||||||
|
If the server starts successfully, you'll see the following output:
|
||||||
|
|
||||||
|
```
|
||||||
|
2024-12-04 14:32:45.584 [main] INFO Application - Application started in 0.303 seconds.
|
||||||
|
2024-12-04 14:32:45.682 [main] INFO Application - Responding at http://0.0.0.0:8080
|
||||||
|
```
|
||||||
+24
-29
@@ -1,38 +1,33 @@
|
|||||||
val ktor_version: String by project
|
|
||||||
val kotlin_version: String by project
|
|
||||||
val logback_version: String by project
|
|
||||||
val kmongo_version: String by project
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.8.22"
|
alias(libs.plugins.kotlin.jvm)
|
||||||
id("io.ktor.plugin") version "2.3.1"
|
alias(ktorLibs.plugins.ktor)
|
||||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.22"
|
alias(libs.plugins.kotlin.serialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "com.ray650128.pcredive"
|
group = "club.sakunadaisuki"
|
||||||
version = "0.0.1"
|
version = "1.0.0-SNAPSHOT"
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("io.ktor.server.netty.EngineMain")
|
mainClass = "io.ktor.server.netty.EngineMain"
|
||||||
|
|
||||||
val isDevelopment: Boolean = project.ext.has("development")
|
|
||||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
kotlin {
|
||||||
mavenCentral()
|
jvmToolchain(21)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-server-cors-jvm:$ktor_version")
|
implementation(ktorLibs.serialization.kotlinx.json)
|
||||||
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
|
implementation(ktorLibs.server.config.yaml)
|
||||||
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
|
implementation(ktorLibs.server.contentNegotiation)
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
|
implementation(ktorLibs.server.core)
|
||||||
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
|
implementation(ktorLibs.server.cors)
|
||||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
implementation(ktorLibs.server.netty)
|
||||||
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
|
implementation(ktorLibs.server.routingOpenapi)
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
implementation(ktorLibs.server.swagger)
|
||||||
|
implementation(libs.logback.classic)
|
||||||
|
implementation(libs.mongodb.bson)
|
||||||
|
implementation(libs.mongodb.driverCore)
|
||||||
|
implementation(libs.mongodb.driverSync)
|
||||||
|
|
||||||
implementation("org.litote.kmongo:kmongo:$kmongo_version")
|
testImplementation(kotlin("test"))
|
||||||
implementation("org.litote.kmongo:kmongo-id-serialization:$kmongo_version")
|
testImplementation(ktorLibs.server.testHost)
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: clan-battle-record-backend
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ${DATA_DIR}:/app/data
|
||||||
|
restart: always
|
||||||
+1
-4
@@ -1,5 +1,2 @@
|
|||||||
ktor_version=2.3.1
|
|
||||||
kotlin_version=1.8.22
|
|
||||||
logback_version=1.2.11
|
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kmongo_version=4.5.0
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[versions]
|
||||||
|
kotlin = "2.4.0"
|
||||||
|
logback = "1.5.35"
|
||||||
|
mongodb = "5.8.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
|
mongodb-bson = { module = "org.mongodb:bson", version.ref = "mongodb" }
|
||||||
|
mongodb-driverCore = { module = "org.mongodb:mongodb-driver-core", version.ref = "mongodb" }
|
||||||
|
mongodb-driverSync = { module = "org.mongodb:mongodb-driver-sync", version.ref = "mongodb" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||||
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
Vendored
BIN
Binary file not shown.
+5
-1
@@ -1,5 +1,9 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright © 2015-2021 the original authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -15,10 +15,12 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# Gradle start up script for POSIX generated by Gradle.
|
# gradlew start up script for POSIX generated by Gradle.
|
||||||
#
|
#
|
||||||
# Important for running:
|
# Important for running:
|
||||||
#
|
#
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
# bash, then to run this script, type that shell name before the whole
|
# bash, then to run this script, type that shell name before the whole
|
||||||
# command line, like:
|
# command line, like:
|
||||||
#
|
#
|
||||||
# ksh Gradle
|
# ksh gradlew
|
||||||
#
|
#
|
||||||
# Busybox and similar reduced shells will NOT work, because this script
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
# requires all of these POSIX shell features:
|
# requires all of these POSIX shell features:
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +82,11 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -114,7 +114,6 @@ case "$( uname )" in #(
|
|||||||
NONSTOP* ) nonstop=true ;;
|
NONSTOP* ) nonstop=true ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
@@ -133,22 +132,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -165,7 +171,6 @@ fi
|
|||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if "$cygwin" || "$msys" ; then
|
if "$cygwin" || "$msys" ; then
|
||||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
|
||||||
|
|
||||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
@@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
-classpath "$CLASSPATH" \
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|||||||
Vendored
+28
-35
@@ -13,19 +13,22 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem gradlew startup script for Windows
|
||||||
@rem
|
@rem
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
|
||||||
@rem Set local scope for the variables with windows NT shell
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
setlocal EnableExtensions
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -40,15 +43,15 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:findJavaFromJavaHome
|
:findJavaFromJavaHome
|
||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
@@ -56,34 +59,24 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute gradlew
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
:end
|
:exitWithErrorLevel
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
|
|
||||||
:fail
|
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
|
||||||
rem the _cmd.exe /c_ return code!
|
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:mainEnd
|
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
|
||||||
|
|
||||||
:omega
|
|
||||||
|
|||||||
+22
-1
@@ -1 +1,22 @@
|
|||||||
rootProject.name = "PcReDivePveRecordBackend"
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
versionCatalogs {
|
||||||
|
create("ktorLibs").from("io.ktor:ktor-version-catalog:3.5.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "PcReDiveClanRecordBackend"
|
||||||
|
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.database.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.bson.Document
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BattleRecordDto(
|
||||||
|
val date: String, // yyyy-MM-dd
|
||||||
|
var uid: String,
|
||||||
|
@SerialName("player_name") val playerName: String,
|
||||||
|
@SerialName("discord_name") val discordName: String,
|
||||||
|
@SerialName("discord_id") val discordID: String,
|
||||||
|
@SerialName("attack_detail_list") val attackDetailList: List<AttackDetailDto>
|
||||||
|
) {
|
||||||
|
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromDocument(document: Document): BattleRecordDto = json.decodeFromString(document.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AttackDetailDto(
|
||||||
|
@SerialName("boss_round") val bossRound: Int, // 周目
|
||||||
|
@SerialName("boss_index") val bossIndex: Int, // 1~5 號王
|
||||||
|
val damage: Long, // 傷害
|
||||||
|
@SerialName("is_compensation") val isCompensation: Boolean = false, // true: 補償刀, false: 正刀
|
||||||
|
@SerialName("is_dropped") val isDropped: Boolean = false, // true: 掉刀, false: 沒掉刀
|
||||||
|
@SerialName("created_at") val createdAt: Long = Instant.now().toEpochMilli(),
|
||||||
|
@SerialName("updated_at") val updatedAt: Long = Instant.now().toEpochMilli()
|
||||||
|
) {
|
||||||
|
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromDocument(document: Document): AttackDetailDto = json.decodeFromString(document.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.database.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.bson.Document
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MemberDto(
|
||||||
|
@SerialName("uid") var uid: String,
|
||||||
|
@SerialName("player_name") val playerName: String,
|
||||||
|
@SerialName("discord_name") val discordName: String,
|
||||||
|
@SerialName("discord_id") val discordID: String,
|
||||||
|
@SerialName("leave") var leave: Boolean,
|
||||||
|
@SerialName("created_at") var createAt: Long? = null,
|
||||||
|
@SerialName("updated_at") var updatedAt: Long? = null
|
||||||
|
) {
|
||||||
|
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromDocument(document: Document): MemberDto = json.decodeFromString(document.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.database.service
|
||||||
|
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.MemberDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.mapper.toEmptyBattleRecordDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecord
|
||||||
|
import com.mongodb.client.MongoCollection
|
||||||
|
import com.mongodb.client.MongoDatabase
|
||||||
|
import com.mongodb.client.model.Filters
|
||||||
|
import com.mongodb.client.model.Filters.and
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.bson.Document
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.YearMonth
|
||||||
|
|
||||||
|
class BattleRecordService(private val database: MongoDatabase) {
|
||||||
|
var collection: MongoCollection<Document>
|
||||||
|
var memberCollection: MongoCollection<Document>
|
||||||
|
|
||||||
|
init {
|
||||||
|
database.createCollection("battle_records")
|
||||||
|
collection = database.getCollection("battle_records")
|
||||||
|
|
||||||
|
database.createCollection("member")
|
||||||
|
memberCollection = database.getCollection("member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new battle record
|
||||||
|
suspend fun create(data: BattleRecordDto): BattleRecordDto = withContext(Dispatchers.IO) {
|
||||||
|
val doc = data.toDocument()
|
||||||
|
collection.insertOne(doc)
|
||||||
|
doc.let(BattleRecordDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a battle record
|
||||||
|
suspend fun read(date: String): List<BattleRecordDto> = withContext(Dispatchers.IO) {
|
||||||
|
collection.find(Filters.eq("date", date)).toList().map(BattleRecordDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a battle record
|
||||||
|
suspend fun read(date: String, uid: String): BattleRecordDto? = withContext(Dispatchers.IO) {
|
||||||
|
val filter = and(
|
||||||
|
Filters.eq("date", date),
|
||||||
|
Filters.eq("uid", uid)
|
||||||
|
)
|
||||||
|
collection.find(filter).first()?.let(BattleRecordDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update a battle record
|
||||||
|
suspend fun update(date: String, data: BattleRecordDto): BattleRecordDto? = withContext(Dispatchers.IO) {
|
||||||
|
val filter = and(
|
||||||
|
Filters.eq("date", date),
|
||||||
|
Filters.eq("uid", data.uid)
|
||||||
|
)
|
||||||
|
collection.findOneAndReplace(filter, data.toDocument())
|
||||||
|
collection.find(filter).first()?.let(BattleRecordDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a battle record
|
||||||
|
suspend fun delete(date: String, uid: String): Document? = withContext(Dispatchers.IO) {
|
||||||
|
val filter = and(
|
||||||
|
Filters.eq("date", date),
|
||||||
|
Filters.eq("uid", uid)
|
||||||
|
)
|
||||||
|
collection.findOneAndDelete(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createTable(date: String): List<BattleRecordDto> = withContext(Dispatchers.IO) {
|
||||||
|
val memberList = memberCollection.find(Filters.eq("leave", false)).toList().map(MemberDto::fromDocument)
|
||||||
|
val emptyBattleRecordList = memberList.map { it.toEmptyBattleRecordDto(date) }
|
||||||
|
collection.insertMany(emptyBattleRecordList.map { it.toDocument() })
|
||||||
|
collection.find(Filters.eq("date", date)).toList().map(BattleRecordDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createTableMonth(date: String): List<List<BattleRecordDto>> = withContext(Dispatchers.IO) {
|
||||||
|
val memberList = memberCollection.find(Filters.eq("leave", false)).toList().map(MemberDto::fromDocument)
|
||||||
|
|
||||||
|
// 1. 指定年份與月份(例如:2026年8月)
|
||||||
|
val dateSplit = date.split("-").map { it.toInt() }
|
||||||
|
val yearMonth = YearMonth.of(dateSplit[0], dateSplit[1])
|
||||||
|
|
||||||
|
// 2. 取得該月最後一天的 LocalDate 物件
|
||||||
|
val lastDayOfMonth = yearMonth.atEndOfMonth()
|
||||||
|
|
||||||
|
// 3. 取得最後 5 天的日期列表(由舊到新排序)
|
||||||
|
val lastFiveDays: List<LocalDate> = (4 downTo 0).map { lastDayOfMonth.minusDays(it.toLong()) }
|
||||||
|
|
||||||
|
// 4. Temp list
|
||||||
|
val tables = mutableListOf<List<BattleRecordDto>>()
|
||||||
|
|
||||||
|
// 5. 寫入資料
|
||||||
|
lastFiveDays.forEach { date ->
|
||||||
|
val emptyBattleRecordList = memberList.map { it.toEmptyBattleRecordDto(date.toString()) }
|
||||||
|
tables.add(emptyBattleRecordList)
|
||||||
|
collection.insertMany(emptyBattleRecordList.map { it.toDocument() })
|
||||||
|
}
|
||||||
|
tables
|
||||||
|
}
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.database.service
|
||||||
|
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.MemberDto
|
||||||
|
import com.mongodb.client.MongoCollection
|
||||||
|
import com.mongodb.client.MongoDatabase
|
||||||
|
import com.mongodb.client.model.Filters
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.bson.Document
|
||||||
|
|
||||||
|
class MemberService(private val database: MongoDatabase) {
|
||||||
|
var collection: MongoCollection<Document>
|
||||||
|
|
||||||
|
init {
|
||||||
|
database.createCollection("member")
|
||||||
|
collection = database.getCollection("member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new member
|
||||||
|
suspend fun create(data: MemberDto): MemberDto = withContext(Dispatchers.IO) {
|
||||||
|
data.createAt = System.currentTimeMillis()
|
||||||
|
data.updatedAt = System.currentTimeMillis()
|
||||||
|
data.leave = false
|
||||||
|
val doc = data.toDocument()
|
||||||
|
collection.insertOne(doc)
|
||||||
|
doc.let(MemberDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a member
|
||||||
|
suspend fun read(): List<MemberDto> = withContext(Dispatchers.IO) {
|
||||||
|
collection.find().toList().map(MemberDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a member
|
||||||
|
suspend fun read(uid: String): MemberDto? = withContext(Dispatchers.IO) {
|
||||||
|
collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update a member
|
||||||
|
suspend fun update(uid: String, data: MemberDto): MemberDto? = withContext(Dispatchers.IO) {
|
||||||
|
collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument)?.copy(
|
||||||
|
playerName = data.playerName,
|
||||||
|
discordName = data.discordName,
|
||||||
|
discordID = data.discordID,
|
||||||
|
leave = data.leave,
|
||||||
|
updatedAt = System.currentTimeMillis(),
|
||||||
|
)?.let {
|
||||||
|
collection.findOneAndReplace(Filters.eq("uid", uid), it.toDocument())
|
||||||
|
}
|
||||||
|
collection.find(Filters.eq("uid", uid)).first()?.let(MemberDto::fromDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a member
|
||||||
|
suspend fun delete(uid: String): Document? = withContext(Dispatchers.IO) {
|
||||||
|
collection.findOneAndDelete(Filters.eq("uid", uid))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend
|
||||||
|
|
||||||
|
import io.ktor.server.netty.EngineMain
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
EngineMain.main(args)
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.mapper
|
||||||
|
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.AttackDetailDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.AttackDetail
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecord
|
||||||
|
|
||||||
|
fun BattleRecord.toDto(): BattleRecordDto = BattleRecordDto(
|
||||||
|
date = date,
|
||||||
|
uid = uid,
|
||||||
|
playerName = playerName,
|
||||||
|
discordName = discordName,
|
||||||
|
discordID = discordID,
|
||||||
|
attackDetailList = attackDetailList.map { it.toDto() },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AttackDetail.toDto(): AttackDetailDto = AttackDetailDto(
|
||||||
|
bossRound = bossRound,
|
||||||
|
bossIndex = bossIndex,
|
||||||
|
damage = damage,
|
||||||
|
isCompensation = isCompensation,
|
||||||
|
isDropped = isDropped,
|
||||||
|
createdAt = createdAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
|
)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.mapper
|
||||||
|
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.MemberDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.Member
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.MemberRequest
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.UpdateMemberRequest
|
||||||
|
|
||||||
|
fun Member.toDto(): MemberDto {
|
||||||
|
return MemberDto(
|
||||||
|
uid = uid,
|
||||||
|
playerName = playerName,
|
||||||
|
discordName = discordName,
|
||||||
|
discordID = discordID,
|
||||||
|
leave = leave,
|
||||||
|
createAt = createAt,
|
||||||
|
updatedAt = updatedAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MemberRequest.toDto(): MemberDto {
|
||||||
|
return MemberDto(
|
||||||
|
uid = uid,
|
||||||
|
playerName = playerName,
|
||||||
|
discordName = discordName,
|
||||||
|
discordID = discordID,
|
||||||
|
leave = leave,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun UpdateMemberRequest.toDto(uid: String): MemberDto {
|
||||||
|
return MemberDto(
|
||||||
|
uid = uid,
|
||||||
|
playerName = playerName,
|
||||||
|
discordName = discordName,
|
||||||
|
discordID = discordID,
|
||||||
|
leave = leave,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MemberDto.toEmptyBattleRecordDto(date: String): BattleRecordDto {
|
||||||
|
return BattleRecordDto(
|
||||||
|
date = date,
|
||||||
|
uid = uid,
|
||||||
|
playerName = playerName,
|
||||||
|
discordName = discordName,
|
||||||
|
discordID = discordID,
|
||||||
|
attackDetailList = emptyList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BattleRecord(
|
||||||
|
@SerialName("date") val date: String, // yyyy-MM-dd
|
||||||
|
@SerialName("uid") var uid: String,
|
||||||
|
@SerialName("player_name") val playerName: String,
|
||||||
|
@SerialName("discord_name") val discordName: String,
|
||||||
|
@SerialName("discord_id") val discordID: String,
|
||||||
|
@SerialName("attack_detail_list") val attackDetailList: List<AttackDetail>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AttackDetail(
|
||||||
|
@SerialName("boss_round") val bossRound: Int, // 周目
|
||||||
|
@SerialName("boss_index") val bossIndex: Int, // 1~5 號王
|
||||||
|
@SerialName("damage") val damage: Long, // 傷害
|
||||||
|
@SerialName("is_compensation") val isCompensation: Boolean, // true: 補償刀, false: 正刀
|
||||||
|
@SerialName("is_dropped") val isDropped: Boolean, // true: 掉刀, false: 沒掉刀
|
||||||
|
@SerialName("created_at") val createdAt: Long = Instant.now().toEpochMilli(),
|
||||||
|
@SerialName("updated_at") val updatedAt: Long = Instant.now().toEpochMilli()
|
||||||
|
)
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BattleRecordRequest(
|
||||||
|
val date: String,
|
||||||
|
val uid: String,
|
||||||
|
@SerialName("player_name") val name: String,
|
||||||
|
@SerialName("discord_name") val discordName: String,
|
||||||
|
@SerialName("discord_id") val discordID: String,
|
||||||
|
val round: Int,
|
||||||
|
val boss: Int,
|
||||||
|
val damage: Long,
|
||||||
|
@SerialName("is_compensation") val isCompensation: Boolean = false,
|
||||||
|
@SerialName("is_dropped") val isDropped: Boolean = false,
|
||||||
|
)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Member(
|
||||||
|
@SerialName("uid") var uid: String,
|
||||||
|
@SerialName("player_name") val playerName: String,
|
||||||
|
@SerialName("discord_name") val discordName: String,
|
||||||
|
@SerialName("discord_id") val discordID: String,
|
||||||
|
@SerialName("leave") var leave: Boolean,
|
||||||
|
@SerialName("created_at") var createAt: Long? = null,
|
||||||
|
@SerialName("updated_at") var updatedAt: Long? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MemberRequest(
|
||||||
|
var uid: String,
|
||||||
|
@SerialName("player_name") val playerName: String,
|
||||||
|
@SerialName("discord_name") val discordName: String,
|
||||||
|
@SerialName("discord_id") val discordID: String,
|
||||||
|
var leave: Boolean = false,
|
||||||
|
)
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UpdateMemberRequest(
|
||||||
|
@SerialName("player_name") val playerName: String,
|
||||||
|
@SerialName("discord_name") val discordName: String,
|
||||||
|
@SerialName("discord_id") val discordID: String,
|
||||||
|
@SerialName("leave") var leave: Boolean,
|
||||||
|
)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
|
||||||
|
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.plugins.cors.routing.*
|
||||||
|
import io.ktor.server.plugins.swagger.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Application.configureHttp() {
|
||||||
|
install(CORS) {
|
||||||
|
allowHost("localhost:8080")
|
||||||
|
allowHost("table.sakunadaisuki.club")
|
||||||
|
allowHost("192.168.50.128:10002")
|
||||||
|
|
||||||
|
allowMethod(HttpMethod.Options)
|
||||||
|
allowMethod(HttpMethod.Put)
|
||||||
|
allowMethod(HttpMethod.Delete)
|
||||||
|
allowMethod(HttpMethod.Patch)
|
||||||
|
allowHeader(HttpHeaders.Authorization)
|
||||||
|
|
||||||
|
allowHeader(HttpHeaders.ContentType) // 處理 JSON 必備
|
||||||
|
allowHeader(HttpHeaders.Authorization) // 傳遞 JWT / 身份驗證權限必備
|
||||||
|
|
||||||
|
allowHeader("MyCustomHeader")
|
||||||
|
//anyHost() // @TODO: Don't do this in production if possible. Try to limit it.
|
||||||
|
}
|
||||||
|
routing {
|
||||||
|
swaggerUI(path = "openapi") {
|
||||||
|
/*
|
||||||
|
Documentation source configuration goes here.
|
||||||
|
|
||||||
|
This can be from file (documentation.yaml), or it can be served dynamically from your sources using the
|
||||||
|
`describe {}` API on routes. When `openApi` enabled in Gradle, these calls will be automatically injected
|
||||||
|
based on your code and comments.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
|
||||||
|
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.dto.BattleRecordDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.service.BattleRecordService
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.database.service.MemberService
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.mapper.toDto
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.AttackDetail
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecord
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.BattleRecordRequest
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.MemberRequest
|
||||||
|
import club.sakunadaisuki.pcrediveclanrecordbackend.model.UpdateMemberRequest
|
||||||
|
import com.mongodb.client.MongoClients
|
||||||
|
import com.mongodb.client.MongoDatabase
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.config.*
|
||||||
|
import io.ktor.server.request.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Application.configureMongo() {
|
||||||
|
// Connect to your mongo instance
|
||||||
|
val mongoDatabase = connectToMongoDB()
|
||||||
|
val memberService = runCatching {
|
||||||
|
MemberService(mongoDatabase)
|
||||||
|
}.getOrNull() ?: return
|
||||||
|
val battleRecordService = runCatching {
|
||||||
|
BattleRecordService(mongoDatabase)
|
||||||
|
}.getOrNull() ?: return
|
||||||
|
|
||||||
|
routing {
|
||||||
|
route("/api") {
|
||||||
|
// Read member
|
||||||
|
get("/member") {
|
||||||
|
memberService.read().let { member ->
|
||||||
|
call.respond(HttpStatusCode.OK, member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read member
|
||||||
|
get("/member/{uid}") {
|
||||||
|
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||||
|
memberService.read(uid)?.let { member ->
|
||||||
|
call.respond(HttpStatusCode.OK, member)
|
||||||
|
} ?: call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create member
|
||||||
|
post("/member") {
|
||||||
|
val member = call.receive<MemberRequest>()
|
||||||
|
val existMember = memberService.read(member.uid)
|
||||||
|
if (existMember == null) {
|
||||||
|
val id = memberService.create(member.toDto())
|
||||||
|
call.respond(HttpStatusCode.Created, id)
|
||||||
|
} else {
|
||||||
|
call.respond(HttpStatusCode.Conflict)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update member
|
||||||
|
put("/member/{uid}") {
|
||||||
|
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||||
|
val member = call.receive<UpdateMemberRequest>()
|
||||||
|
memberService.update(uid, member.toDto(uid))?.let { member ->
|
||||||
|
call.respond(HttpStatusCode.OK, member)
|
||||||
|
} ?: call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete member
|
||||||
|
delete("/member/{uid}") {
|
||||||
|
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||||
|
memberService.delete(uid)?.let {
|
||||||
|
call.respond(HttpStatusCode.OK)
|
||||||
|
} ?: call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Battle Record
|
||||||
|
get("/battle_record/{date}") {
|
||||||
|
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
|
||||||
|
battleRecordService.read(date).let { battleRecord ->
|
||||||
|
call.respond(HttpStatusCode.OK, battleRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get("/battle_record/{date}/{uid}") {
|
||||||
|
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
|
||||||
|
val uid = call.parameters["uid"] ?: throw IllegalArgumentException("No ID found")
|
||||||
|
battleRecordService.read(date, uid)?.let { battleRecord ->
|
||||||
|
call.respond(HttpStatusCode.OK, battleRecord)
|
||||||
|
} ?: call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
put("/battle_record") {
|
||||||
|
val request = call.receive<BattleRecord>()
|
||||||
|
battleRecordService.update(request.date, request.toDto())?.let { battleRecord ->
|
||||||
|
call.respond(HttpStatusCode.OK, battleRecord)
|
||||||
|
} ?: call.respond(HttpStatusCode.NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/battle_record") {
|
||||||
|
val request = call.receive<BattleRecordRequest>()
|
||||||
|
val newAttackDetail = AttackDetail(
|
||||||
|
bossRound = request.round,
|
||||||
|
bossIndex = request.boss,
|
||||||
|
damage = request.damage,
|
||||||
|
isCompensation = request.isCompensation,
|
||||||
|
isDropped = request.isDropped,
|
||||||
|
)
|
||||||
|
val existData = battleRecordService.read(request.date, request.uid)
|
||||||
|
if (existData != null) {
|
||||||
|
val attackDetails = existData.attackDetailList.toMutableList()
|
||||||
|
attackDetails.add(newAttackDetail.toDto())
|
||||||
|
val newData = existData.copy(
|
||||||
|
attackDetailList = attackDetails
|
||||||
|
)
|
||||||
|
battleRecordService.update(request.date, newData)?.let {
|
||||||
|
call.respond(HttpStatusCode.OK, it)
|
||||||
|
} ?: call.respond(HttpStatusCode.NotFound)
|
||||||
|
} else {
|
||||||
|
val newData = BattleRecordDto(
|
||||||
|
date = request.date,
|
||||||
|
uid = request.uid,
|
||||||
|
playerName = request.name,
|
||||||
|
discordName = request.discordName,
|
||||||
|
discordID = request.discordID,
|
||||||
|
attackDetailList = listOf(newAttackDetail.toDto())
|
||||||
|
)
|
||||||
|
val id = battleRecordService.create(newData)
|
||||||
|
call.respond(HttpStatusCode.Created, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/battle_record/create_table_day/{date}") {
|
||||||
|
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
|
||||||
|
val exist = battleRecordService.read(date)
|
||||||
|
if (exist.isNotEmpty()) {
|
||||||
|
call.respond(HttpStatusCode.Conflict)
|
||||||
|
return@post
|
||||||
|
}
|
||||||
|
battleRecordService.createTable(date).let { battleRecord ->
|
||||||
|
call.respond(HttpStatusCode.OK, battleRecord)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post("/battle_record/create_table_month/{date}") {
|
||||||
|
val date = call.parameters["date"] ?: throw IllegalArgumentException("No date")
|
||||||
|
battleRecordService.createTableMonth(date).let { battleRecordList ->
|
||||||
|
call.respond(HttpStatusCode.OK, battleRecordList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes connection with a MongoDB database.
|
||||||
|
*
|
||||||
|
* The following configuration properties (in application.yaml/application.conf) can be specified:
|
||||||
|
* * `db.mongo.user` username for your database
|
||||||
|
* * `db.mongo.password` password for the user
|
||||||
|
* * `db.mongo.host` host that will be used for the database connection
|
||||||
|
* * `db.mongo.port` port that will be used for the database connection
|
||||||
|
* * `db.mongo.maxPoolSize` maximum number of connections to a MongoDB server
|
||||||
|
* * `db.mongo.database.name` name of the database
|
||||||
|
*
|
||||||
|
* IMPORTANT NOTE: in order to make MongoDB connection working, you have to start a MongoDB server first.
|
||||||
|
* See the instructions here: https://www.mongodb.com/docs/manual/administration/install-community/
|
||||||
|
* all the paramaters above
|
||||||
|
*
|
||||||
|
* @returns [MongoDatabase] instance
|
||||||
|
* */
|
||||||
|
fun Application.connectToMongoDB(): MongoDatabase {
|
||||||
|
val user = environment.config.tryGetString("db.mongo.user")
|
||||||
|
val password = environment.config.tryGetString("db.mongo.password")
|
||||||
|
val host = environment.config.tryGetString("db.mongo.host") ?: "127.0.0.1"
|
||||||
|
val port = environment.config.tryGetString("db.mongo.port") ?: "27017"
|
||||||
|
val maxPoolSize = environment.config.tryGetString("db.mongo.maxPoolSize")?.toInt() ?: 20
|
||||||
|
val databaseName = environment.config.tryGetString("db.mongo.database.name") ?: "pc_re_dive_clan_battle"
|
||||||
|
|
||||||
|
val credentials = user?.let { userVal -> password?.let { passwordVal -> "$userVal:$passwordVal@" } }.orEmpty()
|
||||||
|
val uri = "mongodb://$credentials$host:$port/?maxPoolSize=$maxPoolSize&w=majority"
|
||||||
|
|
||||||
|
val mongoClient = MongoClients.create(uri)
|
||||||
|
val database = mongoClient.getDatabase(databaseName)
|
||||||
|
|
||||||
|
monitor.subscribe(ApplicationStopped) {
|
||||||
|
mongoClient.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return database
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
|
||||||
|
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Application.configureRouting() {
|
||||||
|
routing {
|
||||||
|
get("/") {
|
||||||
|
call.respondText("Hello, World!")
|
||||||
|
}
|
||||||
|
get("/json/kotlinx-serialization") {
|
||||||
|
call.respond(mapOf("hello" to "world"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package club.sakunadaisuki.pcrediveclanrecordbackend.plugins
|
||||||
|
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
|
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||||
|
|
||||||
|
fun Application.configureSerialization() {
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
json()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.ray650128.pcredive
|
|
||||||
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import com.ray650128.pcredive.plugins.*
|
|
||||||
|
|
||||||
fun main(args: Array<String>): Unit =
|
|
||||||
io.ktor.server.netty.EngineMain.main(args)
|
|
||||||
|
|
||||||
@Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused.
|
|
||||||
fun Application.module() {
|
|
||||||
configureHTTP()
|
|
||||||
configureSerialization()
|
|
||||||
configureMemberRouting()
|
|
||||||
configureMemberRecordRouting()
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.dto
|
|
||||||
|
|
||||||
import com.ray650128.pcredive.database.model.Member
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MemberDto(
|
|
||||||
var _id: String? = null,
|
|
||||||
var playerName: String,
|
|
||||||
var nickName: String,
|
|
||||||
var discordID: String,
|
|
||||||
var uid: String,
|
|
||||||
var leave: Boolean = false,
|
|
||||||
var createAt: Long? = null,
|
|
||||||
var updatedAt: Long? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
fun Member.toDto(): MemberDto {
|
|
||||||
return MemberDto(
|
|
||||||
_id = this._id.toString(),
|
|
||||||
playerName = this.playerName,
|
|
||||||
nickName = this.nickName,
|
|
||||||
discordID = this.discordID,
|
|
||||||
uid = this.uid,
|
|
||||||
leave = this.leave,
|
|
||||||
createAt = this.createAt,
|
|
||||||
updatedAt = this.updatedAt
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MemberDto.toMember(): Member =
|
|
||||||
Member(
|
|
||||||
playerName = this.playerName,
|
|
||||||
nickName = this.nickName,
|
|
||||||
discordID = this.discordID,
|
|
||||||
uid = this.uid,
|
|
||||||
leave = this.leave,
|
|
||||||
createAt = this.createAt,
|
|
||||||
updatedAt = this.updatedAt
|
|
||||||
)
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.dto
|
|
||||||
|
|
||||||
import com.ray650128.pcredive.database.model.Member
|
|
||||||
import com.ray650128.pcredive.database.model.MemberRecord
|
|
||||||
import com.ray650128.pcredive.database.model.Record
|
|
||||||
import com.ray650128.pcredive.database.service.MemberService
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MemberRecordDto(
|
|
||||||
var _id: String? = null,
|
|
||||||
var member: Member? = null,
|
|
||||||
var record1: Record,
|
|
||||||
var record1c: Record,
|
|
||||||
var record2: Record,
|
|
||||||
var record2c: Record,
|
|
||||||
var record3: Record,
|
|
||||||
var record3c: Record,
|
|
||||||
var createdAt: Long?,
|
|
||||||
var updatedAt: Long?
|
|
||||||
)
|
|
||||||
|
|
||||||
fun MemberRecord.toDto(): MemberRecordDto {
|
|
||||||
return MemberRecordDto(
|
|
||||||
_id = this._id.toString(),
|
|
||||||
member = MemberService.findById(this.memberId.toString()),
|
|
||||||
record1 = this.record1,
|
|
||||||
record1c = this.record1c,
|
|
||||||
record2 = this.record2,
|
|
||||||
record2c = this.record2c,
|
|
||||||
record3 = this.record3,
|
|
||||||
record3c = this.record3c,
|
|
||||||
createdAt = this.createdAt,
|
|
||||||
updatedAt = this.updatedAt
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MemberRecordDto.toMemberRecord(): MemberRecord =
|
|
||||||
MemberRecord(
|
|
||||||
memberId = this.member?._id,
|
|
||||||
record1 = this.record1,
|
|
||||||
record1c = this.record1c,
|
|
||||||
record2 = this.record2,
|
|
||||||
record2c = this.record2c,
|
|
||||||
record3 = this.record3,
|
|
||||||
record3c = this.record3c,
|
|
||||||
createdAt = this.createdAt,
|
|
||||||
updatedAt = this.updatedAt
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.model
|
|
||||||
|
|
||||||
import kotlinx.serialization.Contextual
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import org.litote.kmongo.Id
|
|
||||||
import org.litote.kmongo.newId
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Member(
|
|
||||||
@Contextual var _id: Id<Member> = newId(),
|
|
||||||
var playerName: String,
|
|
||||||
var nickName: String,
|
|
||||||
var discordID: String,
|
|
||||||
var uid: String,
|
|
||||||
var leave: Boolean = false,
|
|
||||||
var createAt: Long? = null,
|
|
||||||
var updatedAt: Long? = null
|
|
||||||
)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.model
|
|
||||||
|
|
||||||
import kotlinx.serialization.Contextual
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import org.litote.kmongo.Id
|
|
||||||
import org.litote.kmongo.newId
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MemberRecord(
|
|
||||||
@Contextual var _id: Id<MemberRecord> = newId(),
|
|
||||||
@Contextual var memberId: Id<Member>? = null,
|
|
||||||
var record1: Record = Record("未出"),
|
|
||||||
var record1c: Record = Record("未出"),
|
|
||||||
var record2: Record = Record("未出"),
|
|
||||||
var record2c: Record = Record("未出"),
|
|
||||||
var record3: Record = Record("未出"),
|
|
||||||
var record3c: Record = Record("未出"),
|
|
||||||
var createdAt: Long? = null,
|
|
||||||
var updatedAt: Long? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Record(
|
|
||||||
var boss: String,
|
|
||||||
var damage: Int? = null
|
|
||||||
)
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.model
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class RecordData(
|
|
||||||
val member: Member,
|
|
||||||
var record: MemberRecord
|
|
||||||
)
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.model
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class UpdateMemberRecord(
|
|
||||||
var memberId: String,
|
|
||||||
var record1: Record = Record("未出"),
|
|
||||||
var record1c: Record = Record("未出"),
|
|
||||||
var record2: Record = Record("未出"),
|
|
||||||
var record2c: Record = Record("未出"),
|
|
||||||
var record3: Record = Record("未出"),
|
|
||||||
var record3c: Record = Record("未出"),
|
|
||||||
var createdAt: Long? = null,
|
|
||||||
var updatedAt: Long? = null
|
|
||||||
)
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.service
|
|
||||||
|
|
||||||
import com.ray650128.pcredive.database.model.Member
|
|
||||||
import com.ray650128.pcredive.database.model.MemberRecord
|
|
||||||
import org.bson.types.ObjectId
|
|
||||||
import org.litote.kmongo.*
|
|
||||||
import org.litote.kmongo.id.toId
|
|
||||||
|
|
||||||
object MemberRecordService {
|
|
||||||
private val client = KMongo.createClient("mongodb://ray650128:Zx650128!@www.ray650128.com:27017")
|
|
||||||
private val database = client.getDatabase("pc_re_dive_clan_battle")
|
|
||||||
private val recordCollection = database.getCollection<MemberRecord>()
|
|
||||||
|
|
||||||
fun create(record: MemberRecord): Id<MemberRecord> {
|
|
||||||
recordCollection.insertOne(record)
|
|
||||||
return record._id
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findById(id: String): MemberRecord? {
|
|
||||||
val bsonId: Id<MemberRecord> = ObjectId(id).toId()
|
|
||||||
return recordCollection.findOne(MemberRecord::_id eq bsonId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findByTimeBetween(start: Long, end: Long): List<MemberRecord> {
|
|
||||||
return recordCollection.find(MemberRecord::createdAt gte start, MemberRecord::createdAt lt end).toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findByOwnerId(id: String): List<MemberRecord> {
|
|
||||||
val bsonId: Id<Member> = ObjectId(id).toId()
|
|
||||||
return recordCollection.find(MemberRecord::memberId eq bsonId).toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateById(id: String, request: MemberRecord): Boolean =
|
|
||||||
findById(id)?.let { record ->
|
|
||||||
val updateResult = recordCollection.replaceOne(
|
|
||||||
record.copy(
|
|
||||||
record1 = request.record1,
|
|
||||||
record1c = request.record1c,
|
|
||||||
record2 = request.record2,
|
|
||||||
record2c = request.record2c,
|
|
||||||
record3 = request.record3,
|
|
||||||
record3c = request.record3c,
|
|
||||||
updatedAt = System.currentTimeMillis()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
updateResult.modifiedCount == 1L
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
fun deleteById(id: String): Boolean {
|
|
||||||
val deleteResult = recordCollection.deleteOneById(ObjectId(id))
|
|
||||||
return deleteResult.deletedCount == 1L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.ray650128.pcredive.database.service
|
|
||||||
|
|
||||||
import com.ray650128.pcredive.database.model.Member
|
|
||||||
import org.bson.types.ObjectId
|
|
||||||
import org.litote.kmongo.*
|
|
||||||
import org.litote.kmongo.id.toId
|
|
||||||
|
|
||||||
object MemberService {
|
|
||||||
private val client = KMongo.createClient("mongodb://ray650128:Zx650128!@www.ray650128.com:27017")
|
|
||||||
private val database = client.getDatabase("pc_re_dive_clan_battle")
|
|
||||||
private val memberCollection = database.getCollection<Member>()
|
|
||||||
|
|
||||||
fun create(member: Member): Id<Member> {
|
|
||||||
memberCollection.insertOne(member)
|
|
||||||
return member._id
|
|
||||||
}
|
|
||||||
|
|
||||||
//fun findAll(): List<Member> = memberCollection.find().toList()
|
|
||||||
|
|
||||||
fun findAll(isLeave: Boolean? = null): List<Member> {
|
|
||||||
return if (isLeave == null) {
|
|
||||||
memberCollection.find().toList()
|
|
||||||
} else {
|
|
||||||
memberCollection.find(Member::leave eq isLeave).toList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findById(id: String): Member? {
|
|
||||||
val bsonId: Id<Member> = ObjectId(id).toId()
|
|
||||||
return memberCollection.findOne(Member::_id eq bsonId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateById(id: String, request: Member): Boolean =
|
|
||||||
findById(id)?.let { member ->
|
|
||||||
val updateResult = memberCollection.replaceOne(
|
|
||||||
member.copy(
|
|
||||||
playerName = request.playerName,
|
|
||||||
nickName = request.nickName,
|
|
||||||
discordID = request.discordID,
|
|
||||||
uid = request.uid,
|
|
||||||
leave = request.leave,
|
|
||||||
updatedAt = request.updatedAt
|
|
||||||
)
|
|
||||||
)
|
|
||||||
updateResult.modifiedCount == 1L
|
|
||||||
} ?: false
|
|
||||||
|
|
||||||
fun deleteById(id: String): Boolean {
|
|
||||||
val deleteResult = memberCollection.deleteOneById(ObjectId(id))
|
|
||||||
return deleteResult.deletedCount == 1L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.ray650128.pcredive.plugins
|
|
||||||
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.server.plugins.cors.routing.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
|
|
||||||
fun Application.configureHTTP() {
|
|
||||||
install(CORS) {
|
|
||||||
allowMethod(HttpMethod.Options)
|
|
||||||
allowMethod(HttpMethod.Put)
|
|
||||||
allowMethod(HttpMethod.Delete)
|
|
||||||
allowMethod(HttpMethod.Patch)
|
|
||||||
allowHeader(HttpHeaders.Authorization)
|
|
||||||
allowHeader(HttpHeaders.AccessControlAllowOrigin)
|
|
||||||
anyHost()
|
|
||||||
allowCredentials = true
|
|
||||||
allowNonSimpleContentTypes = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package com.ray650128.pcredive.plugins
|
|
||||||
|
|
||||||
import com.ray650128.pcredive.database.dto.toDto
|
|
||||||
import com.ray650128.pcredive.database.model.MemberRecord
|
|
||||||
import com.ray650128.pcredive.database.model.Record
|
|
||||||
import com.ray650128.pcredive.database.model.RecordData
|
|
||||||
import com.ray650128.pcredive.database.model.UpdateMemberRecord
|
|
||||||
import com.ray650128.pcredive.database.service.MemberRecordService
|
|
||||||
import com.ray650128.pcredive.database.service.MemberService
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.request.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.util.*
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import org.litote.kmongo.toId
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
fun Application.configureMemberRecordRouting() {
|
|
||||||
routing {
|
|
||||||
route("/api") {
|
|
||||||
route("/record") {
|
|
||||||
get {
|
|
||||||
val calendar = Calendar.getInstance().apply {
|
|
||||||
timeInMillis = System.currentTimeMillis() - (5 * 60 * 60 * 1000) // 由於公連換日為每日早上5:00,因此減去時差
|
|
||||||
}
|
|
||||||
val records = getData(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH))
|
|
||||||
call.respond(HttpStatusCode.OK, records)
|
|
||||||
}
|
|
||||||
post("/{id}") {
|
|
||||||
val id = call.parameters.getOrFail<String>("id")
|
|
||||||
val record = call.receive<MemberRecord>()
|
|
||||||
val member = MemberService.findById(id) ?: run {
|
|
||||||
call.respond(HttpStatusCode.NotFound)
|
|
||||||
return@post
|
|
||||||
}
|
|
||||||
record.apply {
|
|
||||||
memberId = member._id
|
|
||||||
createdAt = System.currentTimeMillis()
|
|
||||||
updatedAt = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
MemberRecordService.create(record).let {
|
|
||||||
call.respond(HttpStatusCode.OK, record)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get("/{year}/{month}/{day}") {
|
|
||||||
val year = call.parameters.getOrFail<Int>("year").toInt()
|
|
||||||
val month = call.parameters.getOrFail<Int>("month").toInt() - 1
|
|
||||||
val day = call.parameters.getOrFail<Int>("day").toInt()
|
|
||||||
val records = getData(year, month, day)
|
|
||||||
call.respond(HttpStatusCode.OK, records)
|
|
||||||
}
|
|
||||||
get("/{id}") {
|
|
||||||
// Show an article with a specific id
|
|
||||||
val id = call.parameters.getOrFail<String>("id")
|
|
||||||
val record = MemberRecordService.findById(id)?.toDto() ?: run {
|
|
||||||
call.respond(HttpStatusCode.NotFound)
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
call.respond(HttpStatusCode.OK, record)
|
|
||||||
}
|
|
||||||
put("/{id}") {
|
|
||||||
val id = call.parameters.getOrFail<String>("id")
|
|
||||||
val record = call.receive<UpdateMemberRecord>()
|
|
||||||
val oldData = MemberRecordService.findById(id) ?: run {
|
|
||||||
call.respond(HttpStatusCode.NotFound)
|
|
||||||
return@put
|
|
||||||
}
|
|
||||||
oldData.apply {
|
|
||||||
record1 = record.record1
|
|
||||||
record1c = record.record1c
|
|
||||||
record2 = record.record2
|
|
||||||
record2c = record.record2c
|
|
||||||
record3 = record.record3
|
|
||||||
record3c = record.record3c
|
|
||||||
updatedAt = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
MemberRecordService.updateById(id, oldData)
|
|
||||||
call.respond(HttpStatusCode.OK, record)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getData(year: Int, month: Int, day: Int): List<RecordData> {
|
|
||||||
val calendar = Calendar.getInstance().apply {
|
|
||||||
set(Calendar.YEAR, year)
|
|
||||||
set(Calendar.MONTH, month)
|
|
||||||
set(Calendar.DAY_OF_MONTH, day)
|
|
||||||
}
|
|
||||||
val startTime = calendar.apply {
|
|
||||||
set(Calendar.HOUR_OF_DAY, 5)
|
|
||||||
set(Calendar.MINUTE, 0)
|
|
||||||
set(Calendar.SECOND, 0)
|
|
||||||
set(Calendar.MILLISECOND, 0)
|
|
||||||
}.timeInMillis
|
|
||||||
val endTime = startTime + 86399000L
|
|
||||||
// Show a list of articles
|
|
||||||
val records = ArrayList<RecordData>()
|
|
||||||
val memberList = MemberService.findAll()
|
|
||||||
memberList.forEach { member ->
|
|
||||||
records.add(
|
|
||||||
RecordData(member = member, MemberRecord())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val recordList = MemberRecordService.findByTimeBetween(startTime, endTime)
|
|
||||||
recordList.forEach { record ->
|
|
||||||
val player = MemberService.findById(record.memberId.toString())
|
|
||||||
records.firstOrNull { it.member == player }?.let { playerRecord ->
|
|
||||||
playerRecord.record = record
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return records
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package com.ray650128.pcredive.plugins
|
|
||||||
|
|
||||||
import com.ray650128.pcredive.database.dto.toDto
|
|
||||||
import com.ray650128.pcredive.database.model.Member
|
|
||||||
import com.ray650128.pcredive.database.model.MemberRecord
|
|
||||||
import com.ray650128.pcredive.database.service.MemberService
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.request.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import io.ktor.server.util.*
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
|
|
||||||
fun Application.configureMemberRouting() {
|
|
||||||
routing {
|
|
||||||
route("/api") {
|
|
||||||
route("/member") {
|
|
||||||
get {
|
|
||||||
// 顯示所有登錄的成員資料
|
|
||||||
coroutineScope {
|
|
||||||
val memberList = MemberService.findAll()
|
|
||||||
call.respond(HttpStatusCode.OK, memberList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get("/{id}") {
|
|
||||||
val id = call.parameters.getOrFail<String>("id")
|
|
||||||
val member = MemberService.findById(id) ?: run {
|
|
||||||
call.respond(HttpStatusCode.NotFound)
|
|
||||||
return@get
|
|
||||||
}
|
|
||||||
call.respond(HttpStatusCode.OK, member)
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
val member = call.receive<Member>()
|
|
||||||
member.apply {
|
|
||||||
createAt = System.currentTimeMillis()
|
|
||||||
updatedAt = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
MemberService.create(member).let {
|
|
||||||
call.respond(HttpStatusCode.OK, member)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
put("/{id}") {
|
|
||||||
val id = call.parameters.getOrFail<String>("id")
|
|
||||||
val member = call.receive<Member>()
|
|
||||||
val oldData = MemberService.findById(id) ?: run {
|
|
||||||
call.respond(HttpStatusCode.NotFound)
|
|
||||||
return@put
|
|
||||||
}
|
|
||||||
oldData.apply {
|
|
||||||
playerName = member.playerName
|
|
||||||
nickName = member.nickName
|
|
||||||
discordID = member.discordID
|
|
||||||
uid = member.uid
|
|
||||||
updatedAt = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
MemberService.updateById(id, oldData)
|
|
||||||
call.respond(HttpStatusCode.OK, oldData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.ray650128.pcredive.plugins
|
|
||||||
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import io.ktor.server.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.server.response.*
|
|
||||||
import io.ktor.server.application.*
|
|
||||||
import io.ktor.server.routing.*
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule
|
|
||||||
|
|
||||||
fun Application.configureSerialization() {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(
|
|
||||||
Json { serializersModule = IdKotlinXSerializationModule }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
ktor {
|
|
||||||
deployment {
|
|
||||||
port = 10001
|
|
||||||
port = ${?PORT}
|
|
||||||
}
|
|
||||||
application {
|
|
||||||
modules = [ com.ray650128.pcredive.ApplicationKt.module ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
ktor:
|
||||||
|
deployment:
|
||||||
|
port: 10001
|
||||||
|
application:
|
||||||
|
modules:
|
||||||
|
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.HttpKt.configureHttp
|
||||||
|
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.SerializationKt.configureSerialization
|
||||||
|
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.MongoKt.configureMongo
|
||||||
|
- club.sakunadaisuki.pcrediveclanrecordbackend.plugins.RoutingKt.configureRouting
|
||||||
|
db:
|
||||||
|
mongo:
|
||||||
|
user: ray650128
|
||||||
|
password: Zx0987062271!
|
||||||
|
host: 192.168.50.128
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
openapi: "3.0.3"
|
||||||
|
info:
|
||||||
|
title: "Application API"
|
||||||
|
description: "Application API"
|
||||||
|
version: "1.0.0"
|
||||||
|
servers:
|
||||||
|
- url: "http://0.0.0.0:8080"
|
||||||
|
paths:
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
description: "Hello World!"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: "string"
|
||||||
|
examples:
|
||||||
|
Example#1:
|
||||||
|
value: "Hello World!"
|
||||||
|
components:
|
||||||
|
schemas: { }
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="trace">
|
<root level="INFO">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="org.eclipse.jetty" level="INFO"/>
|
<logger name="org.eclipse.jetty" level="INFO"/>
|
||||||
<logger name="io.netty" level="INFO"/>
|
<logger name="io.netty" level="INFO"/>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
html {
|
||||||
|
background-image: url("../img/background.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
background-color: #ffffffbb;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 1px 2px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #2c6a99;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 未訪問超連結 */
|
||||||
|
a:link {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 已訪問超連結 */
|
||||||
|
a:visited {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑鼠移動到超連結上 */
|
||||||
|
a:hover {
|
||||||
|
color: #1070c4;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 滑鼠點選時 */
|
||||||
|
a:active {
|
||||||
|
color: #0d5da3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6, label {
|
||||||
|
font-family: 'Arial', system-ui;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centerText {
|
||||||
|
font-family: 'Arial', system-ui;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'Arial', system-ui
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], input[type="number"] {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #07407d;
|
||||||
|
background-color: #ffffffe0;
|
||||||
|
color: #000000;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: 'Arial', system-ui
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"], input[type="number"] {
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="date"] {
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: #07407d;
|
||||||
|
background-color: #ffffffe0;
|
||||||
|
color: #000000;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: 'Arial', system-ui
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="date"] {
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: 1px solid #07407d;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #4ab1ff;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#4ab1ff), to(#0d5da3));
|
||||||
|
background: -moz-linear-gradient(top, #4ab1ff, #0d5da3);
|
||||||
|
background: linear-gradient(to bottom, #4ab1ff, #0d5da3);
|
||||||
|
text-shadow: #591717 1px 1px 1px;
|
||||||
|
font-family: 'Arial', system-ui;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
button:hover,
|
||||||
|
button:focus {
|
||||||
|
border: 1px solid #0b66c8;
|
||||||
|
background: #59d4ff;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#59d4ff), to(#1070c4));
|
||||||
|
background: -moz-linear-gradient(top, #59d4ff, #1070c4);
|
||||||
|
background: linear-gradient(to bottom, #59d4ff, #1070c4);
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
background: #2c6a99;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#2c6a99), to(#0d5da3));
|
||||||
|
background: -moz-linear-gradient(top, #2c6a99, #0d5da3);
|
||||||
|
background: linear-gradient(to bottom, #2c6a99, #0d5da3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_red {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border: 1px solid #7d0707;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ff4a4a;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#ff4a4a), to(#a30d0d));
|
||||||
|
background: -moz-linear-gradient(top, #ff4a4a, #a30d0d);
|
||||||
|
background: linear-gradient(to bottom, #ff4a4a, #a30d0d);
|
||||||
|
text-shadow: #591717 1px 1px 1px;
|
||||||
|
font-family: 'Arial', system-ui;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.button_red:hover,
|
||||||
|
.button_red:focus {
|
||||||
|
border: 1px solid #c80b0b;
|
||||||
|
background: #ff5959;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#ff5959), to(#c41010));
|
||||||
|
background: -moz-linear-gradient(top, #ff5959, #c41010);
|
||||||
|
background: linear-gradient(to bottom, #ff5959, #c41010);
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.button_red:active {
|
||||||
|
background: #992c2c;
|
||||||
|
background: -webkit-gradient(linear, left top, left bottom, from(#992c2c), to(#a30d0d));
|
||||||
|
background: -moz-linear-gradient(top, #992c2c, #a30d0d);
|
||||||
|
background: linear-gradient(to bottom, #992c2c, #a30d0d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autosize-text {
|
||||||
|
font-size: 5vw;
|
||||||
|
font-family: 'Arial', system-ui;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html
|
||||||
|
lang="zh-TW"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<title>新增/修改紀錄</title>
|
||||||
|
<link rel="stylesheet" href="./css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1 class="autosize-text" id="my_title">新增/修改紀錄</h1>
|
||||||
|
<hr />
|
||||||
|
<h4><p>填寫方式:掉刀填0,正常出刀填X周-Y王。</p></h4>
|
||||||
|
<h4><p>傷害依照幹部規定要不要填,如不填可留空</p></h4>
|
||||||
|
<hr />
|
||||||
|
<form id="record-form">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 15%">
|
||||||
|
<h5 class="centerText">第1刀<br />正刀</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 25%">
|
||||||
|
<input type="text" id="knife1_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td style="width: 20%">
|
||||||
|
<h5 class="centerText">第1刀<br />正刀傷害</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 40%">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="knife1_damage"
|
||||||
|
placeholder="ex: 12000000"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 15%">
|
||||||
|
<h5 class="centerText">第1刀<br />殘刀</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 25%">
|
||||||
|
<input type="text" id="knife1c_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td style="width: 20%">
|
||||||
|
<h5 class="centerText">第1刀<br />殘刀傷害</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 40%">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="knife1c_damage"
|
||||||
|
placeholder="ex: 12000000"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 15%">
|
||||||
|
<h5 class="centerText">第2刀<br />正刀</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 25%">
|
||||||
|
<input type="text" id="knife2_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td style="width: 20%">
|
||||||
|
<h5 class="centerText">第2刀<br />正刀傷害</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 40%">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="knife2_damage"
|
||||||
|
placeholder="ex: 12000000"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 15%">
|
||||||
|
<h5 class="centerText">第2刀<br />殘刀</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 25%">
|
||||||
|
<input type="text" id="knife2c_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td style="width: 20%">
|
||||||
|
<h5 class="centerText">第2刀<br />殘刀傷害</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 40%">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="knife2c_damage"
|
||||||
|
placeholder="ex: 12000000"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 15%">
|
||||||
|
<h5 class="centerText">第3刀<br />正刀</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 25%">
|
||||||
|
<input type="text" id="knife3_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td style="width: 20%">
|
||||||
|
<h5 class="centerText">第3刀<br />正刀傷害</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 40%">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="knife3_damage"
|
||||||
|
placeholder="ex: 12000000"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 15%">
|
||||||
|
<h5 class="centerText">第3刀<br />殘刀</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 25%">
|
||||||
|
<input type="text" id="knife3c_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td style="width: 20%">
|
||||||
|
<h5 class="centerText">第3刀<br />殘刀傷害</h5>
|
||||||
|
</td>
|
||||||
|
<td style="width: 40%">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="knife3c_damage"
|
||||||
|
placeholder="ex: 12000000"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<p><button type="submit">送出</button></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
|
||||||
|
<script src="./js/edit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-TW" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<title>公主連結戰隊戰填表系統</title>
|
||||||
|
<link rel="stylesheet" href="./css/style.css">
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="body">
|
||||||
|
<h1 class="centerText">填表系統</h1>
|
||||||
|
<hr>
|
||||||
|
<!--p>
|
||||||
|
<a href="new.html">我要填表</a>
|
||||||
|
</p-->
|
||||||
|
<div class="center">
|
||||||
|
<label for="queryDate">日期篩選:</label>
|
||||||
|
<input type="date" id="queryDate">
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
|
||||||
|
<script src="./js/index.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
var paramValue = "";
|
||||||
|
var memberId = "";
|
||||||
|
|
||||||
|
var title = document.getElementById("my_title");
|
||||||
|
|
||||||
|
var knife1_boss = document.getElementById("knife1_boss");
|
||||||
|
var knife1_damage = document.getElementById("knife1_damage");
|
||||||
|
var knife1c_boss = document.getElementById("knife1c_boss");
|
||||||
|
var knife1c_damage = document.getElementById("knife1c_damage");
|
||||||
|
|
||||||
|
var knife2_boss = document.getElementById("knife2_boss");
|
||||||
|
var knife2_damage = document.getElementById("knife2_damage");
|
||||||
|
var knife2c_boss = document.getElementById("knife2c_boss");
|
||||||
|
var knife2c_damage = document.getElementById("knife2c_damage");
|
||||||
|
|
||||||
|
var knife3_boss = document.getElementById("knife3_boss");
|
||||||
|
var knife3_damage = document.getElementById("knife3_damage");
|
||||||
|
var knife3c_boss = document.getElementById("knife3c_boss");
|
||||||
|
var knife3c_damage = document.getElementById("knife3c_damage");
|
||||||
|
|
||||||
|
// 在頁面載入完成後,執行獲取參數的操作
|
||||||
|
window.onload = function () {
|
||||||
|
// 取得參數
|
||||||
|
paramValue = getParameterByName('id');
|
||||||
|
console.log(paramValue);
|
||||||
|
|
||||||
|
axios.get("./api/record/" + paramValue)
|
||||||
|
.then(function (response) {
|
||||||
|
// 在這裡處理回傳的資料
|
||||||
|
var options = response.data;
|
||||||
|
console.log(options);
|
||||||
|
memberId = options.member._id;
|
||||||
|
|
||||||
|
title.innerHTML = options.member.playerName + " 新增/修改紀錄";
|
||||||
|
|
||||||
|
knife1_boss.value = options.record1.boss;
|
||||||
|
knife1_damage.value = options.record1.damage ?? "";
|
||||||
|
knife1c_boss.value = options.record1c.boss;
|
||||||
|
knife1c_damage.value = options.record1c.damage ?? "";
|
||||||
|
|
||||||
|
knife2_boss.value = options.record2.boss;
|
||||||
|
knife2_damage.value = options.record2.damage ?? "";
|
||||||
|
knife2c_boss.value = options.record2c.boss;
|
||||||
|
knife2c_damage.value = options.record2c.damage ?? "";
|
||||||
|
|
||||||
|
knife3_boss.value = options.record3.boss;
|
||||||
|
knife3_damage.value = options.record3.damage ?? "";
|
||||||
|
knife3c_boss.value = options.record3c.boss;
|
||||||
|
knife3c_damage.value = options.record3c.damage ?? "";
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
// 處理錯誤
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('resize', adjustTextSize);
|
||||||
|
|
||||||
|
document.getElementById("record-form").addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault(); // 阻止表單預設提交行為
|
||||||
|
|
||||||
|
// 執行請求
|
||||||
|
fillRecord(
|
||||||
|
paramValue,
|
||||||
|
memberId,
|
||||||
|
knife1_boss.value,
|
||||||
|
knife1_damage.value,
|
||||||
|
knife1c_boss.value,
|
||||||
|
knife1c_damage.value,
|
||||||
|
knife2_boss.value,
|
||||||
|
knife2_damage.value,
|
||||||
|
knife2c_boss.value,
|
||||||
|
knife2c_damage.value,
|
||||||
|
knife3_boss.value,
|
||||||
|
knife3_damage.value,
|
||||||
|
knife3c_boss.value,
|
||||||
|
knife3c_damage.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function adjustTextSize() {
|
||||||
|
var screenWidth = window.innerWidth;
|
||||||
|
var fontSize = screenWidth * 0.04;
|
||||||
|
|
||||||
|
title.fontSize = fontSize + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParameterByName(name) {
|
||||||
|
// 获取 URL 中的参数部分
|
||||||
|
var url = window.location.href;
|
||||||
|
// 对 URL 进行解析
|
||||||
|
var parsedUrl = new URL(url);
|
||||||
|
// 从解析后的 URL 中获取参数值
|
||||||
|
return parsedUrl.searchParams.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillRecord(
|
||||||
|
recordId,
|
||||||
|
memberId,
|
||||||
|
knife1_boss,
|
||||||
|
knife1_damage,
|
||||||
|
knife1c_boss,
|
||||||
|
knife1c_damage,
|
||||||
|
knife2_boss,
|
||||||
|
knife2_damage,
|
||||||
|
knife2c_boss,
|
||||||
|
knife2c_damage,
|
||||||
|
knife3_boss,
|
||||||
|
knife3_damage,
|
||||||
|
knife3c_boss,
|
||||||
|
knife3c_damage
|
||||||
|
) {
|
||||||
|
// 发送登录请求
|
||||||
|
axios.put("./api/record/" + recordId, {
|
||||||
|
memberId: memberId,
|
||||||
|
record1: {
|
||||||
|
boss: knife1_boss,
|
||||||
|
damage: parseInt(knife1_damage),
|
||||||
|
},
|
||||||
|
record1c: {
|
||||||
|
boss: knife1c_boss,
|
||||||
|
damage: parseInt(knife1c_damage),
|
||||||
|
},
|
||||||
|
record2: {
|
||||||
|
boss: knife2_boss,
|
||||||
|
damage: parseInt(knife2_damage),
|
||||||
|
},
|
||||||
|
record2c: {
|
||||||
|
boss: knife2c_boss,
|
||||||
|
damage: parseInt(knife2c_damage),
|
||||||
|
},
|
||||||
|
record3: {
|
||||||
|
boss: knife3_boss,
|
||||||
|
damage: parseInt(knife3_damage),
|
||||||
|
},
|
||||||
|
record3c: {
|
||||||
|
boss: knife3c_boss,
|
||||||
|
damage: parseInt(knife3c_damage),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
window.location.href = "./index.html";
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
const message = error.response.data.message;
|
||||||
|
alert("更新失敗");
|
||||||
|
//console.error('登入失敗:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
// 日期選擇
|
||||||
|
var dateInput = document.getElementById('queryDate');
|
||||||
|
dateInput.addEventListener('change', function () {
|
||||||
|
//console.log("change" + dateInput.value);
|
||||||
|
localStorage.setItem('temp_date', dateInput.value);
|
||||||
|
getDateOfRecords();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 載入日期
|
||||||
|
if (localStorage.getItem('temp_date') !== null) {
|
||||||
|
dateInput.value = localStorage.getItem("temp_date");
|
||||||
|
} else {
|
||||||
|
var today = new Date();
|
||||||
|
var formattedDate = today.toISOString().split('T')[0];
|
||||||
|
dateInput.value = formattedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
let table = document.createElement("table");
|
||||||
|
let thead = document.createElement("thead");
|
||||||
|
let tbody = document.createElement("tbody");
|
||||||
|
generateTable();
|
||||||
|
|
||||||
|
getDateOfRecords();
|
||||||
|
|
||||||
|
setInterval(getDateOfRecords, 2000);
|
||||||
|
|
||||||
|
function generateTable() {
|
||||||
|
table.appendChild(thead);
|
||||||
|
table.appendChild(tbody);
|
||||||
|
|
||||||
|
// Adding the entire table to the body tag
|
||||||
|
document.getElementById("body").appendChild(table);
|
||||||
|
|
||||||
|
// Creating and adding data to first row of the table
|
||||||
|
let row_1 = document.createElement("tr");
|
||||||
|
let heading_1 = document.createElement("th");
|
||||||
|
heading_1.setAttribute('style', 'width: 40%;')
|
||||||
|
let heading_2 = document.createElement("th");
|
||||||
|
heading_2.setAttribute('style', 'width: 10%;')
|
||||||
|
let heading_3 = document.createElement("th");
|
||||||
|
heading_3.setAttribute('style', 'width: 10%;')
|
||||||
|
let heading_4 = document.createElement("th");
|
||||||
|
heading_4.setAttribute('style', 'width: 10%;')
|
||||||
|
let heading_5 = document.createElement("th");
|
||||||
|
heading_5.setAttribute('style', 'width: 10%;')
|
||||||
|
let heading_6 = document.createElement("th");
|
||||||
|
heading_6.setAttribute('style', 'width: 10%;')
|
||||||
|
let heading_7 = document.createElement("th");
|
||||||
|
heading_7.setAttribute('style', 'width: 10%;')
|
||||||
|
|
||||||
|
let heading_1_text = document.createElement("h5");
|
||||||
|
heading_1_text.innerHTML = "成員名稱";
|
||||||
|
let heading_2_text = document.createElement("h5");
|
||||||
|
heading_2_text.innerHTML = "1<br>正刀";
|
||||||
|
let heading_3_text = document.createElement("h5");
|
||||||
|
heading_3_text.innerHTML = "2<br>正刀";
|
||||||
|
let heading_4_text = document.createElement("h5");
|
||||||
|
heading_4_text.innerHTML = "3<br>正刀";
|
||||||
|
let heading_5_text = document.createElement("h5");
|
||||||
|
heading_5_text.innerHTML = "1<br>殘刀";
|
||||||
|
let heading_6_text = document.createElement("h5");
|
||||||
|
heading_6_text.innerHTML = "2<br>殘刀";
|
||||||
|
let heading_7_text = document.createElement("h5");
|
||||||
|
heading_7_text.innerHTML = "3<br>殘刀";
|
||||||
|
|
||||||
|
heading_1.appendChild(heading_1_text);
|
||||||
|
heading_2.appendChild(heading_2_text);
|
||||||
|
heading_3.appendChild(heading_3_text);
|
||||||
|
heading_4.appendChild(heading_4_text);
|
||||||
|
heading_5.appendChild(heading_5_text);
|
||||||
|
heading_6.appendChild(heading_6_text);
|
||||||
|
heading_7.appendChild(heading_7_text);
|
||||||
|
|
||||||
|
row_1.appendChild(heading_1);
|
||||||
|
row_1.appendChild(heading_2);
|
||||||
|
row_1.appendChild(heading_3);
|
||||||
|
row_1.appendChild(heading_4);
|
||||||
|
row_1.appendChild(heading_5);
|
||||||
|
row_1.appendChild(heading_6);
|
||||||
|
row_1.appendChild(heading_7);
|
||||||
|
thead.appendChild(row_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取得刀表
|
||||||
|
function getDateOfRecords() {
|
||||||
|
var dateSplit = dateInput.value.split("-");
|
||||||
|
console.log(dateSplit);
|
||||||
|
// 取得刀表
|
||||||
|
axios.get("./api/record/" + dateSplit[0] + "/" + dateSplit[1] + "/" + dateSplit[2], {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
const data = response.data;
|
||||||
|
//console.log(data);
|
||||||
|
// 清除原有的資料列
|
||||||
|
while (tbody.firstChild) {
|
||||||
|
tbody.removeChild(tbody.firstChild);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
// 建立新的資料列
|
||||||
|
let row_2 = document.createElement("tr");
|
||||||
|
let row_2_data_1 = document.createElement("td");
|
||||||
|
let row_2_data_2 = document.createElement("td");
|
||||||
|
let row_2_data_3 = document.createElement("td");
|
||||||
|
let row_2_data_4 = document.createElement("td");
|
||||||
|
let row_2_data_5 = document.createElement("td");
|
||||||
|
let row_2_data_6 = document.createElement("td");
|
||||||
|
let row_2_data_7 = document.createElement("td");
|
||||||
|
|
||||||
|
const item = data[i];
|
||||||
|
|
||||||
|
if (item.record !== null) {
|
||||||
|
row_2_data_1.innerHTML = "<h5 class=\"centerText\"><a href=\"edit.html?id=" + item.record._id + "\">" + item.member.playerName + "</a></h5>";
|
||||||
|
row_2_data_2.innerHTML = "<h5 class=\"centerText\">" + item.record.record1.boss + "</h5>";
|
||||||
|
row_2_data_3.innerHTML = "<h5 class=\"centerText\">" + item.record.record2.boss + "</h5>";
|
||||||
|
row_2_data_4.innerHTML = "<h5 class=\"centerText\">" + item.record.record3.boss + "</h5>";
|
||||||
|
row_2_data_5.innerHTML = "<h5 class=\"centerText\">" + item.record.record1c.boss + "</h5>";
|
||||||
|
row_2_data_6.innerHTML = "<h5 class=\"centerText\">" + item.record.record2c.boss + "</h5>";
|
||||||
|
row_2_data_7.innerHTML = "<h5 class=\"centerText\">" + item.record.record3c.boss + "</h5>";
|
||||||
|
} else {
|
||||||
|
row_2_data_1.innerHTML = "<h5 class=\"centerText\">" + item.member.playerName + "</h5>";
|
||||||
|
row_2_data_2.innerHTML = "<h5 class=\"centerText\">---</h5>";
|
||||||
|
row_2_data_3.innerHTML = "<h5 class=\"centerText\">---</h5>";
|
||||||
|
row_2_data_4.innerHTML = "<h5 class=\"centerText\">---</h5>";
|
||||||
|
row_2_data_5.innerHTML = "<h5 class=\"centerText\">---</h5>";
|
||||||
|
row_2_data_6.innerHTML = "<h5 class=\"centerText\">---</h5>";
|
||||||
|
row_2_data_7.innerHTML = "<h5 class=\"centerText\">---</h5>";
|
||||||
|
}
|
||||||
|
|
||||||
|
row_2.appendChild(row_2_data_1);
|
||||||
|
row_2.appendChild(row_2_data_2);
|
||||||
|
row_2.appendChild(row_2_data_3);
|
||||||
|
row_2.appendChild(row_2_data_4);
|
||||||
|
row_2.appendChild(row_2_data_5);
|
||||||
|
row_2.appendChild(row_2_data_6);
|
||||||
|
row_2.appendChild(row_2_data_7);
|
||||||
|
tbody.appendChild(row_2);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.error('資料取得失敗:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
document
|
||||||
|
.getElementById("record-form")
|
||||||
|
.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault(); // 阻止表單預設提交行為
|
||||||
|
|
||||||
|
// 假設你有一個下拉式選單的 HTML 元素
|
||||||
|
var selectElement = document.getElementById("member_select");
|
||||||
|
|
||||||
|
var knife1_boss = document.getElementById("knife1_boss").value;
|
||||||
|
var knife1_damage = document.getElementById("knife1_damage").value;
|
||||||
|
var knife1c_boss = document.getElementById("knife1c_boss").value;
|
||||||
|
var knife1c_damage = document.getElementById("knife1c_damage").value;
|
||||||
|
|
||||||
|
var knife2_boss = document.getElementById("knife2_boss").value;
|
||||||
|
var knife2_damage = document.getElementById("knife2_damage").value;
|
||||||
|
var knife2c_boss = document.getElementById("knife2c_boss").value;
|
||||||
|
var knife2c_damage = document.getElementById("knife2c_damage").value;
|
||||||
|
|
||||||
|
var knife3_boss = document.getElementById("knife3_boss").value;
|
||||||
|
var knife3_damage = document.getElementById("knife3_damage").value;
|
||||||
|
var knife3c_boss = document.getElementById("knife3c_boss").value;
|
||||||
|
var knife3c_damage = document.getElementById("knife3c_damage").value;
|
||||||
|
|
||||||
|
// 執行請求
|
||||||
|
fillRecord(
|
||||||
|
selectElement.value,
|
||||||
|
knife1_boss,
|
||||||
|
knife1_damage,
|
||||||
|
knife1c_boss,
|
||||||
|
knife1c_damage,
|
||||||
|
knife2_boss,
|
||||||
|
knife2_damage,
|
||||||
|
knife2c_boss,
|
||||||
|
knife2c_damage,
|
||||||
|
knife3_boss,
|
||||||
|
knife3_damage,
|
||||||
|
knife3c_boss,
|
||||||
|
knife3c_damage
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
axios
|
||||||
|
.get("./api/member/not_leave")
|
||||||
|
.then(function (response) {
|
||||||
|
// 在這裡處理回傳的資料
|
||||||
|
var options = response.data;
|
||||||
|
|
||||||
|
// 假設你有一個下拉式選單的 HTML 元素
|
||||||
|
var selectElement = document.getElementById("member_select");
|
||||||
|
|
||||||
|
// 將資料加入到下拉式選單中
|
||||||
|
options.forEach(function (option) {
|
||||||
|
var optionElement = document.createElement("option");
|
||||||
|
optionElement.value = option._id;
|
||||||
|
optionElement.textContent = option.playerName;
|
||||||
|
selectElement.appendChild(optionElement);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
// 處理錯誤
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
function fillRecord(
|
||||||
|
memberId,
|
||||||
|
knife1_boss,
|
||||||
|
knife1_damage,
|
||||||
|
knife1c_boss,
|
||||||
|
knife1c_damage,
|
||||||
|
knife2_boss,
|
||||||
|
knife2_damage,
|
||||||
|
knife2c_boss,
|
||||||
|
knife2c_damage,
|
||||||
|
knife3_boss,
|
||||||
|
knife3_damage,
|
||||||
|
knife3c_boss,
|
||||||
|
knife3c_damage
|
||||||
|
) {
|
||||||
|
// 发送登录请求
|
||||||
|
axios.post("./api/record/" + memberId, {
|
||||||
|
record1: {
|
||||||
|
boss: knife1_boss,
|
||||||
|
damage: parseInt(knife1_damage),
|
||||||
|
},
|
||||||
|
record1c: {
|
||||||
|
boss: knife1c_boss,
|
||||||
|
damage: parseInt(knife1c_damage),
|
||||||
|
},
|
||||||
|
record2: {
|
||||||
|
boss: knife2_boss,
|
||||||
|
damage: parseInt(knife2_damage),
|
||||||
|
},
|
||||||
|
record2c: {
|
||||||
|
boss: knife2c_boss,
|
||||||
|
damage: parseInt(knife2c_damage),
|
||||||
|
},
|
||||||
|
record3: {
|
||||||
|
boss: knife3_boss,
|
||||||
|
damage: parseInt(knife3_damage),
|
||||||
|
},
|
||||||
|
record3c: {
|
||||||
|
boss: knife3c_boss,
|
||||||
|
damage: parseInt(knife3c_damage),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
window.location.href = "./index.html";
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
const message = error.response.data.message;
|
||||||
|
alert("新增失敗");
|
||||||
|
//console.error('登入失敗:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html
|
||||||
|
lang="zh-TW"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<title>修改成員</title>
|
||||||
|
<link rel="stylesheet" href="../css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1 class="centerText">修改成員</h1>
|
||||||
|
<hr />
|
||||||
|
<form id="member-form">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 30%"><h4>成員名稱</h4></td>
|
||||||
|
<td style="width: 70%">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="nick_name"
|
||||||
|
placeholder="請輸入成員 Discord 名稱"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><h4>遊戲角色名稱</h4></td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="player_name"
|
||||||
|
placeholder="請輸入遊戲主角名稱"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><h4>遊戲UID</h4></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="game_uid" placeholder="請輸入遊戲U ID" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><h4>DC ID</h4></td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="discord_id"
|
||||||
|
placeholder="請輸入 Discord user ID"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><h4>是否離開戰隊</h4></td>
|
||||||
|
<td>
|
||||||
|
<input type="radio" id="leave_true" name="is_leave" value="true" />
|
||||||
|
<label for="leave_true">是</label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="leave_false"
|
||||||
|
name="is_leave"
|
||||||
|
value="false"
|
||||||
|
/>
|
||||||
|
<label for="leave_true">否</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
<button id="submit" type="button">更新</button>
|
||||||
|
<button class="button_red" id="delete" type="button">刪除</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
|
||||||
|
<script src="./js/edit.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-TW" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<title>公主連結戰隊戰填表系統 - 成員管理</title>
|
||||||
|
<link rel="stylesheet" href="../css/style.css">
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="body">
|
||||||
|
<h1 class="centerText">成員管理</h1>
|
||||||
|
<hr>
|
||||||
|
<div class="center">
|
||||||
|
<h4><a href="./new.html">新增成員</a></h4>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
|
||||||
|
<script src="./js/index.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
var paramValue = "";
|
||||||
|
var memberId = "";
|
||||||
|
|
||||||
|
var nickName = document.getElementById("nick_name");
|
||||||
|
var playerName = document.getElementById("player_name");
|
||||||
|
var gameUid = document.getElementById("game_uid");
|
||||||
|
var discordId = document.getElementById("discord_id");
|
||||||
|
|
||||||
|
// 在頁面載入完成後,執行獲取參數的操作
|
||||||
|
window.onload = function () {
|
||||||
|
// 取得參數
|
||||||
|
paramValue = getParameterByName('id');
|
||||||
|
console.log(paramValue);
|
||||||
|
|
||||||
|
axios.get("../api/member/" + paramValue)
|
||||||
|
.then(function (response) {
|
||||||
|
// 在這裡處理回傳的資料
|
||||||
|
var options = response.data;
|
||||||
|
console.log(options);
|
||||||
|
nickName.value = options.nickName;
|
||||||
|
playerName.value = options.playerName;
|
||||||
|
gameUid.value = options.uid;
|
||||||
|
discordId.value = options.discordID;
|
||||||
|
|
||||||
|
if (options.leave) {
|
||||||
|
document.getElementById("leave_true").checked = true;
|
||||||
|
} else {
|
||||||
|
document.getElementById("leave_false").checked = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
// 處理錯誤
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("submit").addEventListener("click", function (event) {
|
||||||
|
event.preventDefault(); // 阻止表單預設提交行為
|
||||||
|
|
||||||
|
// 執行請求
|
||||||
|
updateMember(paramValue, nickName.value, playerName.value, gameUid.value, discordId.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("delete").addEventListener("click", function (event) {
|
||||||
|
event.preventDefault(); // 阻止表單預設提交行為
|
||||||
|
|
||||||
|
// 執行請求
|
||||||
|
deleteMember(paramValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getParameterByName(name) {
|
||||||
|
// 获取 URL 中的参数部分
|
||||||
|
var url = window.location.href;
|
||||||
|
// 对 URL 进行解析
|
||||||
|
var parsedUrl = new URL(url);
|
||||||
|
// 从解析后的 URL 中获取参数值
|
||||||
|
return parsedUrl.searchParams.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMember(memberId, nickName, playerName, gameUid, discordId) {
|
||||||
|
var selected = document.querySelector('input[name="is_leave"]:checked')
|
||||||
|
console.log(selected.value);
|
||||||
|
var isLeave = false;
|
||||||
|
if (selected) {
|
||||||
|
if (selected.value == "true") {
|
||||||
|
isLeave = true;
|
||||||
|
} else {
|
||||||
|
isLeave = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 发送登录请求
|
||||||
|
axios.put("../api/member/" + memberId, {
|
||||||
|
playerName: playerName,
|
||||||
|
nickName: nickName,
|
||||||
|
discordID: discordId,
|
||||||
|
uid: gameUid,
|
||||||
|
leave: isLeave
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
window.location.href = "../index.html";
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
//const message = error.response.data.message;
|
||||||
|
alert("更新失敗");
|
||||||
|
//console.error('登入失敗:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteMember(memberId) {
|
||||||
|
axios.delete("../api/member/" + memberId)
|
||||||
|
.then(function (response) {
|
||||||
|
window.location.href = "./index.html";
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
//const message = error.response.data.message;
|
||||||
|
alert("刪除失敗");
|
||||||
|
//console.error('登入失敗:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
var selectedDate = document.getElementById("queryDate");
|
||||||
|
|
||||||
|
let table = document.createElement("table");
|
||||||
|
let thead = document.createElement("thead");
|
||||||
|
let tbody = document.createElement("tbody");
|
||||||
|
|
||||||
|
table.appendChild(thead);
|
||||||
|
table.appendChild(tbody);
|
||||||
|
|
||||||
|
// Adding the entire table to the body tag
|
||||||
|
document.getElementById("body").appendChild(table);
|
||||||
|
|
||||||
|
// Creating and adding data to first row of the table
|
||||||
|
let row_1 = document.createElement("tr");
|
||||||
|
/*let heading_1 = document.createElement("th");
|
||||||
|
heading_1.innerHTML = "<h4>成員名稱</h4>";*/
|
||||||
|
let heading_2 = document.createElement("th");
|
||||||
|
heading_2.innerHTML = "<h4>角色暱稱</h4>";
|
||||||
|
heading_2.setAttribute('style', 'width: 54%;')
|
||||||
|
let heading_3 = document.createElement("th");
|
||||||
|
heading_3.innerHTML = "<h4>角色UID</h4>";
|
||||||
|
heading_3.setAttribute('style', 'width: 34%;')
|
||||||
|
/*let heading_4 = document.createElement("th");
|
||||||
|
heading_4.innerHTML = "<h4>DC ID</h4>";*/
|
||||||
|
let heading_5 = document.createElement("th");
|
||||||
|
heading_5.innerHTML = "<h4>離開</h4>";
|
||||||
|
heading_5.setAttribute('style', 'width: 12%;')
|
||||||
|
|
||||||
|
//row_1.appendChild(heading_1);
|
||||||
|
row_1.appendChild(heading_2);
|
||||||
|
row_1.appendChild(heading_3);
|
||||||
|
//row_1.appendChild(heading_4);
|
||||||
|
row_1.appendChild(heading_5);
|
||||||
|
thead.appendChild(row_1);
|
||||||
|
// 取得刀表
|
||||||
|
axios.get("../api/member", {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
const data = response.data;
|
||||||
|
console.log(data);
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
// Creating and adding data to second row of the table
|
||||||
|
let row_2 = document.createElement("tr");
|
||||||
|
//let row_2_data_1 = document.createElement("td");
|
||||||
|
let row_2_data_2 = document.createElement("td");
|
||||||
|
let row_2_data_3 = document.createElement("td");
|
||||||
|
//let row_2_data_4 = document.createElement("td");
|
||||||
|
let row_2_data_5 = document.createElement("td");
|
||||||
|
|
||||||
|
const item = data[i];
|
||||||
|
|
||||||
|
//row_2_data_1.innerHTML = "<a href=\"edit.html?id=" + item._id + "\">" + item.nickName + "</a>";
|
||||||
|
row_2_data_2.innerHTML = "<h4><a href=\"edit.html?id=" + item._id + "\">" + item.playerName + "</a></h4>";
|
||||||
|
row_2_data_3.innerHTML = "<h4>" + item.uid + "</h4>";
|
||||||
|
//row_2_data_4.innerHTML = item.discordID;
|
||||||
|
if (item.leave) {
|
||||||
|
row_2_data_5.innerHTML = "<h4>O</h4>";
|
||||||
|
} else {
|
||||||
|
row_2_data_5.innerHTML = "<h4>X</h4>";
|
||||||
|
}
|
||||||
|
|
||||||
|
//row_2.appendChild(row_2_data_1);
|
||||||
|
row_2.appendChild(row_2_data_2);
|
||||||
|
row_2.appendChild(row_2_data_3);
|
||||||
|
//row_2.appendChild(row_2_data_4);
|
||||||
|
row_2.appendChild(row_2_data_5);
|
||||||
|
tbody.appendChild(row_2);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
//const message = error.response.data.message;
|
||||||
|
//alert("資料取得失敗");
|
||||||
|
console.error('資料取得失敗:', error);
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
document
|
||||||
|
.getElementById("member-form")
|
||||||
|
.addEventListener("submit", function (event) {
|
||||||
|
event.preventDefault(); // 阻止表單預設提交行為
|
||||||
|
|
||||||
|
var nickName = document.getElementById("nick_name");
|
||||||
|
var playerName = document.getElementById("player_name");
|
||||||
|
var gameUid = document.getElementById("game_uid");
|
||||||
|
var discordId = document.getElementById("discord_id");
|
||||||
|
|
||||||
|
// 執行請求
|
||||||
|
newMember(nickName.value, playerName.value, gameUid.value, discordId.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
function newMember(nickName, playerName, gameUid, discordId) {
|
||||||
|
// 发送登录请求
|
||||||
|
axios.post("../api/member", {
|
||||||
|
playerName: playerName,
|
||||||
|
nickName: nickName,
|
||||||
|
discordID: discordId,
|
||||||
|
uid: gameUid
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
window.location.href = "../index.html";
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
const message = error.response.data.message;
|
||||||
|
alert("新增失敗");
|
||||||
|
//console.error('登入失敗:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html
|
||||||
|
lang="zh-TW"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<title>新增成員</title>
|
||||||
|
<link rel="stylesheet" href="../css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="body">
|
||||||
|
<h1 class="centerText">新增成員</h1>
|
||||||
|
<hr />
|
||||||
|
<form id="member-form">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 30%"><h4>成員名稱</h4></td>
|
||||||
|
<td style="width: 70%">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="nick_name"
|
||||||
|
placeholder="請輸入成員 Discord 名稱"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><h4>遊戲角色名稱</h4></td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="player_name"
|
||||||
|
placeholder="請輸入遊戲主角名稱"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><h4>遊戲UID</h4></td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="game_uid" placeholder="請輸入遊戲U ID" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><h4>DC ID</h4></td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="discord_id"
|
||||||
|
placeholder="請輸入 Discord user ID"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<p><button type="submit">送出</button></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
|
||||||
|
<script src="./js/new.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html
|
||||||
|
lang="zh-TW"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
xmlns="http://www.w3.org/1999/html"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
<title>新增紀錄</title>
|
||||||
|
<link rel="stylesheet" href="./css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="body">
|
||||||
|
<h1 class="centerText">新增紀錄</h1>
|
||||||
|
<hr />
|
||||||
|
<h4>
|
||||||
|
填寫方式:掉刀填0,正常出刀填X周-Y王。傷害依照幹部規定要不要填,如不填可留空
|
||||||
|
</h4>
|
||||||
|
<hr />
|
||||||
|
<form id="record-form">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>請選擇你的名字</td>
|
||||||
|
<td>
|
||||||
|
<select id="member_select"></select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>第1刀正刀</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife1_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td>第1刀正刀傷害</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife1_damage" placeholder="ex: 12000000" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>第1刀殘刀</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife1c_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td>第1刀殘刀傷害</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife1c_damage" placeholder="ex: 12000000" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>第2刀正刀</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife2_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td>第2刀正刀傷害</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife2_damage" placeholder="ex: 12000000" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>第2刀殘刀</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife2c_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td>第2刀殘刀傷害</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife2c_damage" placeholder="ex: 12000000" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>第3刀正刀</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife3_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td>第3刀正刀傷害</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife3_damage" placeholder="ex: 12000000" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>第3刀殘刀</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife3c_boss" placeholder="x周-y王" />
|
||||||
|
</td>
|
||||||
|
<td>第3刀殘刀傷害</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" id="knife3c_damage" placeholder="ex: 12000000" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
<p><button type="submit">送出</button></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.1.2/dist/axios.min.js"></script>
|
||||||
|
<script src="./js/new.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user