Compare commits
2 Commits
d4fb485bb1
...
20b666e408
| Author | SHA1 | Date |
|---|---|---|
|
|
20b666e408 | |
|
|
179277f637 |
|
|
@ -0,0 +1,3 @@
|
|||
```
|
||||
我需要测试大语言模型的写sql能力,先帮我设计一个数据库和几张表,表之间具有较为复杂的关联关系,例如一对多、多对多等。再添加一些迷惑项,例如相似的字段名、相似的表名、含义相近的字段等。
|
||||
```
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analog Clock</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.clock {
|
||||
width: 80vmin;
|
||||
height: 80vmin;
|
||||
border: 3vmin solid #333;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.clock-number {
|
||||
position: absolute;
|
||||
font-size: 6vmin;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
width: 8vmin;
|
||||
height: 8vmin;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -4vmin;
|
||||
margin-top: -4vmin;
|
||||
}
|
||||
|
||||
.clock-number:nth-child(1) { transform: translateY(-35vmin); } /* 12 */
|
||||
.clock-number:nth-child(2) { transform: translate(17.5vmin, -30.3vmin); } /* 1 */
|
||||
.clock-number:nth-child(3) { transform: translate(30.3vmin, -17.5vmin); } /* 2 */
|
||||
.clock-number:nth-child(4) { transform: translate(35vmin, 0); } /* 3 */
|
||||
.clock-number:nth-child(5) { transform: translate(30.3vmin, 17.5vmin); } /* 4 */
|
||||
.clock-number:nth-child(6) { transform: translate(17.5vmin, 30.3vmin); } /* 5 */
|
||||
.clock-number:nth-child(7) { transform: translateY(35vmin); } /* 6 */
|
||||
.clock-number:nth-child(8) { transform: translate(-17.5vmin, 30.3vmin); } /* 7 */
|
||||
.clock-number:nth-child(9) { transform: translate(-30.3vmin, 17.5vmin); } /* 8 */
|
||||
.clock-number:nth-child(10) { transform: translate(-35vmin, 0); } /* 9 */
|
||||
.clock-number:nth-child(11) { transform: translate(-30.3vmin, -17.5vmin); } /* 10 */
|
||||
.clock-number:nth-child(12) { transform: translate(-17.5vmin, -30.3vmin); } /* 11 */
|
||||
|
||||
.hand {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
border-radius: 2vmin;
|
||||
}
|
||||
|
||||
.hand-hour {
|
||||
width: 1.2vmin;
|
||||
height: 22vmin;
|
||||
background-color: #333;
|
||||
transform: translateX(-50%) rotate(var(--hour-rotation));
|
||||
}
|
||||
|
||||
.hand-minute {
|
||||
width: 0.8vmin;
|
||||
height: 30vmin;
|
||||
background-color: #555;
|
||||
transform: translateX(-50%) rotate(var(--minute-rotation));
|
||||
}
|
||||
|
||||
.hand-second {
|
||||
width: 0.4vmin;
|
||||
height: 34vmin;
|
||||
background-color: #e74c3c;
|
||||
transform: translateX(-50%) rotate(var(--second-rotation));
|
||||
animation: secondTick 60s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes secondTick {
|
||||
from { transform: translateX(-50%) rotate(0deg); }
|
||||
to { transform: translateX(-50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
.center-dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 2.5vmin;
|
||||
height: 2.5vmin;
|
||||
background-color: #333;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="clock">
|
||||
<div class="clock-number">12</div>
|
||||
<div class="clock-number">1</div>
|
||||
<div class="clock-number">2</div>
|
||||
<div class="clock-number">3</div>
|
||||
<div class="clock-number">4</div>
|
||||
<div class="clock-number">5</div>
|
||||
<div class="clock-number">6</div>
|
||||
<div class="clock-number">7</div>
|
||||
<div class="clock-number">8</div>
|
||||
<div class="clock-number">9</div>
|
||||
<div class="clock-number">10</div>
|
||||
<div class="clock-number">11</div>
|
||||
|
||||
<div class="hand hand-hour"></div>
|
||||
<div class="hand hand-minute"></div>
|
||||
<div class="hand hand-second"></div>
|
||||
<div class="center-dot"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
const hours = now.getHours();
|
||||
const minutes = now.getMinutes();
|
||||
const seconds = now.getSeconds();
|
||||
const milliseconds = now.getMilliseconds();
|
||||
|
||||
const secondAngle = (seconds + milliseconds / 1000) * 6;
|
||||
const minuteAngle = (minutes + seconds / 60) * 6;
|
||||
const hourAngle = (hours % 12 + minutes / 60) * 30;
|
||||
|
||||
document.documentElement.style.setProperty('--second-rotation', secondAngle + 'deg');
|
||||
document.documentElement.style.setProperty('--minute-rotation', minuteAngle + 'deg');
|
||||
document.documentElement.style.setProperty('--hour-rotation', hourAngle + 'deg');
|
||||
}
|
||||
|
||||
updateClock();
|
||||
setInterval(updateClock, 100);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analog Clock</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.clock-container {
|
||||
width: 90vmin;
|
||||
height: 90vmin;
|
||||
max-width: 400px;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.clock {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
border: 8px solid #2c3e50;
|
||||
}
|
||||
|
||||
.center-dot {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #e74c3c;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hand {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.hour-hand {
|
||||
width: 8px;
|
||||
height: 30%;
|
||||
background-color: #2c3e50;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.minute-hand {
|
||||
width: 6px;
|
||||
height: 40%;
|
||||
background-color: #34495e;
|
||||
margin-left: -3px;
|
||||
}
|
||||
|
||||
.second-hand {
|
||||
width: 3px;
|
||||
height: 45%;
|
||||
background-color: #e74c3c;
|
||||
margin-left: -1.5px;
|
||||
transition: transform 0.2s cubic-bezier(0.4, 2.3, 0.8, 1);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.number {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 1.8em;
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.number span {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tick {
|
||||
position: absolute;
|
||||
background-color: #7f8c8d;
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
.hour-tick {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
.minute-tick {
|
||||
width: 2px;
|
||||
height: 10px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="clock-container">
|
||||
<div class="clock" id="clock">
|
||||
<div class="center-dot"></div>
|
||||
<div class="hand hour-hand" id="hour-hand"></div>
|
||||
<div class="hand minute-hand" id="minute-hand"></div>
|
||||
<div class="hand second-hand" id="second-hand"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const clock = document.getElementById('clock');
|
||||
const hourHand = document.getElementById('hour-hand');
|
||||
const minuteHand = document.getElementById('minute-hand');
|
||||
const secondHand = document.getElementById('second-hand');
|
||||
|
||||
function createClockFace() {
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
const number = document.createElement('div');
|
||||
number.className = 'number';
|
||||
const angle = (i * 30) * Math.PI / 180;
|
||||
const radius = 45;
|
||||
number.innerHTML = `<span>${i}</span>`;
|
||||
number.style.transform = `translate(-50%, -50%) rotate(${i * 30}deg)`;
|
||||
number.style.left = `${50 + radius * Math.sin(angle)}%`;
|
||||
number.style.top = `${50 - radius * Math.cos(angle)}%`;
|
||||
clock.appendChild(number);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const tick = document.createElement('div');
|
||||
tick.className = i % 5 === 0 ? 'tick hour-tick' : 'tick minute-tick';
|
||||
tick.style.transform = `translateX(-50%) rotate(${i * 6}deg)`;
|
||||
tick.style.bottom = '50%';
|
||||
clock.appendChild(tick);
|
||||
}
|
||||
}
|
||||
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
const hours = now.getHours() % 12;
|
||||
const minutes = now.getMinutes();
|
||||
const seconds = now.getSeconds();
|
||||
const milliseconds = now.getMilliseconds();
|
||||
|
||||
const hourDeg = (hours * 30) + (minutes * 0.5);
|
||||
const minuteDeg = (minutes * 6) + (seconds * 0.1);
|
||||
const secondDeg = (seconds * 6) + (milliseconds * 0.006);
|
||||
|
||||
hourHand.style.transform = `translateX(-50%) rotate(${hourDeg}deg)`;
|
||||
minuteHand.style.transform = `translateX(-50%) rotate(${minuteDeg}deg)`;
|
||||
secondHand.style.transform = `translateX(-50%) rotate(${secondDeg}deg)`;
|
||||
|
||||
requestAnimationFrame(updateClock);
|
||||
}
|
||||
|
||||
createClockFace();
|
||||
updateClock();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Modern Responsive Analog Clock</title>
|
||||
<style>
|
||||
/* CSS Variables for easy theming and sizing */
|
||||
:root {
|
||||
--bg-color: #ffffff;
|
||||
--clock-face: #f9f9f9;
|
||||
--text-color: #333333;
|
||||
--accent-color: #e74c3c; /* Red for second hand */
|
||||
--hand-color: #2c3e50;
|
||||
--shadow-light: rgba(255, 255, 255, 0.8);
|
||||
--shadow-dark: rgba(0, 0, 0, 0.1);
|
||||
--clock-size: min(80vw, 80vh); /* Responsive unit */
|
||||
}
|
||||
|
||||
/* Reset and Base Layout */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: var(--bg-color);
|
||||
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
overflow: hidden; /* Prevent scrollbars if zoomed */
|
||||
}
|
||||
|
||||
/* Main Clock Container */
|
||||
.clock-container {
|
||||
position: relative;
|
||||
width: var(--clock-size);
|
||||
height: var(--clock-size);
|
||||
border-radius: 50%;
|
||||
background: var(--clock-face);
|
||||
/* Soft neumorphic shadow */
|
||||
box-shadow:
|
||||
20px 20px 60px var(--shadow-dark),
|
||||
-20px -20px 60px var(--shadow-light),
|
||||
inset 5px 5px 10px var(--shadow-dark),
|
||||
inset -5px -5px 10px var(--shadow-light);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Clock Face Numbers */
|
||||
.clock-face {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Number positioning using transforms */
|
||||
.number {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
/* Rotate the container to the angle */
|
||||
transform: rotate(calc(var(--rotation) * 1deg));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.number span {
|
||||
display: block;
|
||||
font-size: calc(var(--clock-size) * 0.08);
|
||||
font-weight: 600;
|
||||
/* Counter-rotate the text so it stays upright */
|
||||
transform: rotate(calc(var(--rotation) * -1deg));
|
||||
padding-top: 5%; /* Push number away from absolute center edge */
|
||||
}
|
||||
|
||||
/* Hands Styles */
|
||||
.hand {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
border-radius: 4px;
|
||||
z-index: 10;
|
||||
/* Using CSS variables updated by JS */
|
||||
transform: translateX(-50%) rotate(var(--deg));
|
||||
will-change: transform; /* Performance optimization */
|
||||
}
|
||||
|
||||
/* Hour Hand */
|
||||
.hand.hour {
|
||||
width: calc(var(--clock-size) * 0.025);
|
||||
height: calc(var(--clock-size) * 0.25);
|
||||
background: var(--hand-color);
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
/* Minute Hand */
|
||||
.hand.min {
|
||||
width: calc(var(--clock-size) * 0.015);
|
||||
height: calc(var(--clock-size) * 0.35);
|
||||
background: var(--hand-color);
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
/* Second Hand */
|
||||
.hand.second {
|
||||
width: calc(var(--clock-size) * 0.008);
|
||||
height: calc(var(--clock-size) * 0.40);
|
||||
background: var(--accent-color);
|
||||
z-index: 13;
|
||||
/* Add a tail to the second hand */
|
||||
}
|
||||
|
||||
.hand.second::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 20%;
|
||||
bottom: -20%;
|
||||
left: 0;
|
||||
background: var(--accent-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Center Nut/Pin */
|
||||
.center-nut {
|
||||
position: absolute;
|
||||
width: calc(var(--clock-size) * 0.04);
|
||||
height: calc(var(--clock-size) * 0.04);
|
||||
background: var(--accent-color);
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
z-index: 14;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Digital Time Display (Optional, for accessibility/clarity) */
|
||||
.digital-readout {
|
||||
position: absolute;
|
||||
bottom: 25%;
|
||||
font-size: calc(var(--clock-size) * 0.05);
|
||||
font-family: 'Courier New', monospace;
|
||||
color: var(--text-color);
|
||||
opacity: 0.5;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Semantic Clock Container -->
|
||||
<main class="clock-container" id="clock" aria-label="Analog Clock displaying current time">
|
||||
|
||||
<div class="clock-face">
|
||||
<!-- Numbers 1-12 generated by loop logic below for cleaner HTML -->
|
||||
<!-- Manually placing 12, 3, 6, 9 for structure, others via JS -->
|
||||
<div class="number" style="--rotation: 30"><span>1</span></div>
|
||||
<div class="number" style="--rotation: 60"><span>2</span></div>
|
||||
<div class="number" style="--rotation: 90"><span>3</span></div>
|
||||
<div class="number" style="--rotation: 120"><span>4</span></div>
|
||||
<div class="number" style="--rotation: 150"><span>5</span></div>
|
||||
<div class="number" style="--rotation: 180"><span>6</span></div>
|
||||
<div class="number" style="--rotation: 210"><span>7</span></div>
|
||||
<div class="number" style="--rotation: 240"><span>8</span></div>
|
||||
<div class="number" style="--rotation: 270"><span>9</span></div>
|
||||
<div class="number" style="--rotation: 300"><span>10</span></div>
|
||||
<div class="number" style="--rotation: 330"><span>11</span></div>
|
||||
<div class="number" style="--rotation: 0"><span>12</span></div>
|
||||
|
||||
<!-- Hands -->
|
||||
<div class="hand hour" id="hour-hand"></div>
|
||||
<div class="hand min" id="min-hand"></div>
|
||||
<div class="hand second" id="sec-hand"></div>
|
||||
|
||||
<!-- Center Pin -->
|
||||
<div class="center-nut"></div>
|
||||
|
||||
<!-- Optional small digital readout -->
|
||||
<div class="digital-readout" id="digital-time">00:00:00</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Clock Logic
|
||||
* Uses requestAnimationFrame for a smooth, high-performance update loop
|
||||
* rather than a simple setInterval, ensuring the second hand sweeps smoothly.
|
||||
*/
|
||||
|
||||
const hourHand = document.getElementById('hour-hand');
|
||||
const minHand = document.getElementById('min-hand');
|
||||
const secHand = document.getElementById('sec-hand');
|
||||
const clock = document.getElementById('clock');
|
||||
const digitalReadout = document.getElementById('digital-time');
|
||||
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
|
||||
// Get precise time components
|
||||
const seconds = now.getSeconds();
|
||||
const milliseconds = now.getMilliseconds();
|
||||
const minutes = now.getMinutes();
|
||||
const hours = now.getHours();
|
||||
|
||||
// Calculate degrees
|
||||
|
||||
// Second hand: includes milliseconds for a "sweep" animation
|
||||
// Formula: (seconds + ms/1000) / 60 * 360
|
||||
const secDegrees = ((seconds + (milliseconds / 1000)) / 60) * 360;
|
||||
|
||||
// Minute hand: includes seconds fraction for smooth movement between ticks
|
||||
const minDegrees = ((minutes + seconds / 60) / 60) * 360;
|
||||
|
||||
// Hour hand: includes minutes fraction
|
||||
const hourDegrees = ((hours % 12 + minutes / 60) / 12) * 360;
|
||||
|
||||
// Apply CSS variables to rotate the hands
|
||||
// We set the variable --deg on the specific elements
|
||||
secHand.style.setProperty('--deg', `${secDegrees}deg`);
|
||||
minHand.style.setProperty('--deg', `${minDegrees}deg`);
|
||||
hourHand.style.setProperty('--deg', `${hourDegrees}deg`);
|
||||
|
||||
// Update Accessibility Label (optional but good practice)
|
||||
const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
clock.setAttribute('aria-label', `Current time is ${timeString}`);
|
||||
|
||||
// Update small digital readout
|
||||
digitalReadout.textContent = timeString;
|
||||
|
||||
// Request next frame
|
||||
requestAnimationFrame(updateClock);
|
||||
}
|
||||
|
||||
// Initialize the clock
|
||||
requestAnimationFrame(updateClock);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Responsive Analog Clock</title>
|
||||
<style>
|
||||
:root {
|
||||
--size: min(78vmin, 520px);
|
||||
--border: 8px;
|
||||
--rim: 10px;
|
||||
--tick: #c7cbd1;
|
||||
--face: #ffffff;
|
||||
--numbers: #111;
|
||||
--hands-hour: #111;
|
||||
--hands-minute: #111;
|
||||
--hands-second: #e63946;
|
||||
--center: #111;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: #ffffff; /* white background */
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.clock {
|
||||
position: relative;
|
||||
width: var(--size);
|
||||
aspect-ratio: 1;
|
||||
background: var(--face);
|
||||
border: var(--border) solid #111;
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 12px 28px rgba(0,0,0,.08),
|
||||
0 2px 6px rgba(0,0,0,.05) inset;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Outer decorative rim */
|
||||
.clock::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: calc(var(--border) + var(--rim));
|
||||
border-radius: 50%;
|
||||
border: 2px solid #e6e8eb;
|
||||
}
|
||||
|
||||
/* Minute/hour ticks */
|
||||
.ticks {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.tick {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 2px;
|
||||
height: 8%;
|
||||
background: var(--tick);
|
||||
transform-origin: 50% 50%;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
/* Thicker hour ticks (12) */
|
||||
.tick.hour {
|
||||
width: 4px;
|
||||
height: 12%;
|
||||
background: #8f95a1;
|
||||
}
|
||||
|
||||
/* Numerals */
|
||||
.numeral {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
color: var(--numbers);
|
||||
font-weight: 600;
|
||||
transform-origin: 50% 50%;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
text-shadow: 0 1px 0 rgba(0,0,0,0.05);
|
||||
}
|
||||
.numeral span {
|
||||
display: inline-block;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: clamp(14px, 5.2vmin, 28px);
|
||||
}
|
||||
|
||||
/* Hands */
|
||||
.hand {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform-origin: 50% 100%;
|
||||
transform: translate(-50%, -100%) rotate(0deg);
|
||||
border-radius: 999px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.hand.hour {
|
||||
width: 8px;
|
||||
height: 26%;
|
||||
background: var(--hands-hour);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.hand.minute {
|
||||
width: 6px;
|
||||
height: 36%;
|
||||
background: var(--hands-minute);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.hand.second {
|
||||
width: 3px;
|
||||
height: 42%;
|
||||
background: var(--hands-second);
|
||||
z-index: 5;
|
||||
/* Tail counterweight */
|
||||
}
|
||||
.hand.second::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%);
|
||||
width: 3px;
|
||||
height: 16%;
|
||||
background: var(--hands-second);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Center cap */
|
||||
.center-cap {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transform: translate(-50%, -50%);
|
||||
background: var(--center);
|
||||
border-radius: 50%;
|
||||
z-index: 6;
|
||||
box-shadow: 0 0 0 3px #fff, 0 1px 3px rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
/* CSS-animated second hand (smooth sweep) */
|
||||
.spin {
|
||||
animation-name: sweep;
|
||||
animation-duration: 60s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
animation-play-state: running;
|
||||
}
|
||||
|
||||
@keyframes sweep {
|
||||
from { transform: translate(-50%, -100%) rotate(0deg); }
|
||||
to { transform: translate(-50%, -100%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Accessibility: reduce motion users still get a ticking second hand */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.spin {
|
||||
animation-duration: 1s; /* rapid ticks */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="clock" id="clock">
|
||||
<div class="ticks" aria-hidden="true"></div>
|
||||
<div class="numeral" aria-hidden="true"><span>12</span></div>
|
||||
<div class="numeral" aria-hidden="true"><span>3</span></div>
|
||||
<div class="numeral" aria-hidden="true"><span>6</span></div>
|
||||
<div class="numeral" aria-hidden="true"><span>9</span></div>
|
||||
|
||||
<div class="hand hour" id="hourHand"></div>
|
||||
<div class="hand minute" id="minuteHand"></div>
|
||||
<div class="hand second spin" id="secondHand"></div>
|
||||
|
||||
<div class="center-cap" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const hourHand = document.getElementById('hourHand');
|
||||
const minuteHand = document.getElementById('minuteHand');
|
||||
const secondHand = document.getElementById('secondHand');
|
||||
|
||||
// Create minute and hour ticks (60 total, 12 hour markers)
|
||||
const ticksContainer = document.querySelector('.ticks');
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const tick = document.createElement('div');
|
||||
tick.className = 'tick' + (i % 5 === 0 ? ' hour' : '');
|
||||
tick.style.transform = `translate(-50%, -50%) rotate(${i * 6}deg)`;
|
||||
ticksContainer.appendChild(tick);
|
||||
}
|
||||
|
||||
// Position numerals
|
||||
const numerals = document.querySelectorAll('.numeral');
|
||||
const labels = ['12', '3', '6', '9'];
|
||||
numerals.forEach((el, i) => {
|
||||
const angle = i * 90; // 0,90,180,270
|
||||
el.style.transform = `translate(-50%, -50%) rotate(${angle}deg)`;
|
||||
el.firstElementChild.textContent = labels[i];
|
||||
el.firstElementChild.style.left = '50%';
|
||||
el.firstElementChild.style.top = '50%';
|
||||
});
|
||||
|
||||
// Helpers
|
||||
const setAngle = (el, deg) => {
|
||||
el.style.transform = `translate(-50%, -100%) rotate(${deg}deg)`;
|
||||
};
|
||||
|
||||
// Initial set from system time
|
||||
function updateHands() {
|
||||
const now = new Date();
|
||||
const ms = now.getMilliseconds();
|
||||
const s = now.getSeconds() + ms / 1000;
|
||||
const m = now.getMinutes() + s / 60;
|
||||
const h = (now.getHours() % 12) + m / 60;
|
||||
|
||||
setAngle(hourHand, h * 30);
|
||||
setAngle(minuteHand, m * 6);
|
||||
// Second hand is CSS-animated; align its animation phase to current time.
|
||||
const delay = -(now.getSeconds() + ms / 1000); // start at exact current second
|
||||
secondHand.style.animationDelay = `${delay}s`;
|
||||
}
|
||||
|
||||
// Keep minute/hour hands in sync every minute (minimal JS)
|
||||
updateHands();
|
||||
let minuteTimer = null;
|
||||
const scheduleNextMinuteUpdate = () => {
|
||||
const now = new Date();
|
||||
const msToNextMinute = (60 - now.getSeconds()) * 1000 - now.getMilliseconds() + 25; // +25ms buffer
|
||||
clearTimeout(minuteTimer);
|
||||
minuteTimer = setTimeout(() => {
|
||||
updateHands();
|
||||
scheduleNextMinuteUpdate();
|
||||
}, Math.max(250, msToNextMinute));
|
||||
};
|
||||
scheduleNextMinuteUpdate();
|
||||
|
||||
// Also re-sync on visibility change (when tab becomes active again)
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) updateHands();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Responsive Analog Clock</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f0f2f5;
|
||||
font-family: 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
.clock-container {
|
||||
position: relative;
|
||||
width: min(90vmin, 400px);
|
||||
height: min(90vmin, 400px);
|
||||
}
|
||||
|
||||
.clock {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
border: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.clock-face {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.number {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.number1 { transform: translate(-50%, -50%) rotate(30deg) translateY(-35%) rotate(-30deg); }
|
||||
.number2 { transform: translate(-50%, -50%) rotate(60deg) translateY(-35%) rotate(-60deg); }
|
||||
.number3 { transform: translate(-50%, -50%) rotate(90deg) translateY(-35%) rotate(-90deg); }
|
||||
.number4 { transform: translate(-50%, -50%) rotate(120deg) translateY(-35%) rotate(-120deg); }
|
||||
.number5 { transform: translate(-50%, -50%) rotate(150deg) translateY(-35%) rotate(-150deg); }
|
||||
.number6 { transform: translate(-50%, -50%) rotate(180deg) translateY(-35%) rotate(-180deg); }
|
||||
.number7 { transform: translate(-50%, -50%) rotate(210deg) translateY(-35%) rotate(-210deg); }
|
||||
.number8 { transform: translate(-50%, -50%) rotate(240deg) translateY(-35%) rotate(-240deg); }
|
||||
.number9 { transform: translate(-50%, -50%) rotate(270deg) translateY(-35%) rotate(-270deg); }
|
||||
.number10 { transform: translate(-50%, -50%) rotate(300deg) translateY(-35%) rotate(-300deg); }
|
||||
.number11 { transform: translate(-50%, -50%) rotate(330deg) translateY(-35%) rotate(-330deg); }
|
||||
.number12 { transform: translate(-50%, -50%) rotate(360deg) translateY(-35%) rotate(-360deg); }
|
||||
|
||||
.hand {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
transform: translateX(-50%) rotate(0deg);
|
||||
transition: transform 0.1s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.hour-hand {
|
||||
width: 6px;
|
||||
height: 25%;
|
||||
background-color: #333;
|
||||
border-radius: 3px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.minute-hand {
|
||||
width: 4px;
|
||||
height: 35%;
|
||||
background-color: #333;
|
||||
border-radius: 2px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.second-hand {
|
||||
width: 2px;
|
||||
height: 40%;
|
||||
background-color: #e74c3c;
|
||||
border-radius: 1px;
|
||||
z-index: 1;
|
||||
transition: transform 0.05s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.center-dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: #333;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.center-dot::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.number {
|
||||
font-size: 1rem;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.hour-hand {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.minute-hand {
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.second-hand {
|
||||
width: 1.5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="clock-container">
|
||||
<div class="clock">
|
||||
<div class="clock-face">
|
||||
<div class="number number1">1</div>
|
||||
<div class="number number2">2</div>
|
||||
<div class="number number3">3</div>
|
||||
<div class="number number4">4</div>
|
||||
<div class="number number5">5</div>
|
||||
<div class="number number6">6</div>
|
||||
<div class="number number7">7</div>
|
||||
<div class="number number8">8</div>
|
||||
<div class="number number9">9</div>
|
||||
<div class="number number10">10</div>
|
||||
<div class="number number11">11</div>
|
||||
<div class="number number12">12</div>
|
||||
|
||||
<div class="hand hour-hand"></div>
|
||||
<div class="hand minute-hand"></div>
|
||||
<div class="hand second-hand"></div>
|
||||
<div class="center-dot"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
const hours = now.getHours() % 12;
|
||||
const minutes = now.getMinutes();
|
||||
const seconds = now.getSeconds();
|
||||
const milliseconds = now.getMilliseconds();
|
||||
|
||||
// Calculate angles
|
||||
const hourAngle = (hours * 30) + (minutes * 0.5) + (seconds * 0.008333);
|
||||
const minuteAngle = (minutes * 6) + (seconds * 0.1) + (milliseconds * 0.000166667);
|
||||
const secondAngle = (seconds * 6) + (milliseconds * 0.006);
|
||||
|
||||
// Apply rotations
|
||||
document.querySelector('.hour-hand').style.transform = `translateX(-50%) rotate(${hourAngle}deg)`;
|
||||
document.querySelector('.minute-hand').style.transform = `translateX(-50%) rotate(${minuteAngle}deg)`;
|
||||
document.querySelector('.second-hand').style.transform = `translateX(-50%) rotate(${secondAngle}deg)`;
|
||||
}
|
||||
|
||||
// Update clock immediately and then every millisecond for smooth movement
|
||||
updateClock();
|
||||
setInterval(updateClock, 1);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
|
@ -0,0 +1,514 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analog Clock</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="clock-container">
|
||||
<div class="clock">
|
||||
<!-- Clock face numbers -->
|
||||
<div class="number number-1"><span>1</span></div>
|
||||
<div class="number number-2"><span>2</span></div>
|
||||
<div class="number number-3"><span>3</span></div>
|
||||
<div class="number number-4"><span>4</span></div>
|
||||
<div class="number number-5"><span>5</span></div>
|
||||
<div class="number number-6"><span>6</span></div>
|
||||
<div class="number number-7"><span>7</span></div>
|
||||
<div class="number number-8"><span>8</span></div>
|
||||
<div class="number number-9"><span>9</span></div>
|
||||
<div class="number number-10"><span>10</span></div>
|
||||
<div class="number number-11"><span>11</span></div>
|
||||
<div class="number number-12"><span>12</span></div>
|
||||
|
||||
<!-- Clock hands -->
|
||||
<div class="hand hour-hand" id="hour-hand"></div>
|
||||
<div class="hand minute-hand" id="minute-hand"></div>
|
||||
<div class="hand second-hand" id="second-hand"></div>
|
||||
|
||||
<!-- Center pivot -->
|
||||
<div class="center-pivot"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Iframe 元素高亮注入脚本
|
||||
* 需要在目标网站中引入此脚本来支持跨域 iframe 高亮功能
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 将此脚本添加到目标网站的 HTML 中
|
||||
* 2. 或通过浏览器扩展、用户脚本等方式注入
|
||||
*/
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// 检查是否在 iframe 中
|
||||
if (window.self === window.top) {
|
||||
return; // 不在 iframe 中,不执行
|
||||
}
|
||||
|
||||
// 检查是否已经初始化过
|
||||
if (window.__iframeHighlightInitialized) {
|
||||
return;
|
||||
}
|
||||
window.__iframeHighlightInitialized = true;
|
||||
console.log("Iframe 高亮脚本已加载");
|
||||
|
||||
// 创建高亮覆盖层
|
||||
var overlay = document.createElement("div");
|
||||
overlay.id = "iframe-highlight-overlay";
|
||||
overlay.style.cssText = "\n position: fixed;\n top: 0;\n left: 0;\n width: 100vw;\n height: 100vh;\n pointer-events: none;\n z-index: 999999;\n overflow: hidden;\n ";
|
||||
|
||||
// 创建悬停高亮框(虚线边框)
|
||||
var highlightBox = document.createElement("div");
|
||||
highlightBox.id = "iframe-highlight-box";
|
||||
highlightBox.style.cssText = "\n position: absolute;\n border: 2px dashed #007AFF;\n background: rgba(0, 122, 255, 0.08);\n pointer-events: none;\n display: none;\n transition: all 0.1s ease;\n box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.8);\n border-radius: 2px;\n ";
|
||||
|
||||
// 创建选中节点的常驻高亮框(实线边框)
|
||||
var selectedBox = document.createElement("div");
|
||||
selectedBox.id = "iframe-selected-box";
|
||||
selectedBox.style.cssText = "\n position: absolute;\n border: 2px solid #007AFF;\n pointer-events: none;\n display: none;\n box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.9), 0 0 8px rgba(255, 107, 53, 0.4);\n border-radius: 2px;\n z-index: 1000000;\n ";
|
||||
|
||||
// 创建悬停标签显示
|
||||
var tagLabel = document.createElement("div");
|
||||
tagLabel.id = "iframe-tag-label";
|
||||
tagLabel.style.cssText = "\n position: absolute;\n background: #007AFF;\n color: white;\n padding: 2px 6px;\n font-size: 11px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n border-radius: 2px;\n pointer-events: none;\n display: none;\n white-space: nowrap;\n z-index: 1000001;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n font-weight: 500;\n ";
|
||||
|
||||
// 创建选中节点标签
|
||||
var selectedLabel = document.createElement("div");
|
||||
selectedLabel.id = "iframe-selected-label";
|
||||
selectedLabel.style.cssText = "\n position: absolute;\n background: #007AFF;\n color: white;\n padding: 3px 8px;\n font-size: 11px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n border-radius: 3px;\n pointer-events: none;\n display: none;\n white-space: nowrap;\n z-index: 1000002;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);\n font-weight: 600;\n ";
|
||||
overlay.appendChild(highlightBox);
|
||||
overlay.appendChild(selectedBox);
|
||||
overlay.appendChild(tagLabel);
|
||||
overlay.appendChild(selectedLabel);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// 存储当前选中的元素
|
||||
var selectedElement = null;
|
||||
var highlightEnabled = false;
|
||||
|
||||
// 更新选中元素的高亮显示
|
||||
function updateSelectedHighlight(element) {
|
||||
console.log("updateSelectedHighlight called with:", element);
|
||||
if (!element) {
|
||||
selectedBox.style.display = "none";
|
||||
selectedLabel.style.display = "none";
|
||||
selectedElement = null;
|
||||
console.log("Cleared selected highlight");
|
||||
return;
|
||||
}
|
||||
selectedElement = element;
|
||||
var rect = element.getBoundingClientRect();
|
||||
console.log("Selected element rect:", rect);
|
||||
|
||||
// 更新选中高亮框位置
|
||||
selectedBox.style.display = "block";
|
||||
selectedBox.style.left = "".concat(rect.left - 2, "px");
|
||||
selectedBox.style.top = "".concat(rect.top - 2, "px");
|
||||
selectedBox.style.width = "".concat(rect.width + 4, "px");
|
||||
selectedBox.style.height = "".concat(rect.height + 4, "px");
|
||||
|
||||
// 更新选中标签位置和内容
|
||||
selectedLabel.style.display = "block";
|
||||
selectedLabel.textContent = "\u2713 <".concat(element.tagName.toLowerCase(), ">");
|
||||
|
||||
// 计算标签位置,确保不超出视窗
|
||||
var labelTop = rect.top - 28;
|
||||
var labelLeft = rect.left;
|
||||
|
||||
// 如果标签会超出顶部,显示在元素下方
|
||||
if (labelTop < 5) {
|
||||
labelTop = rect.bottom + 5;
|
||||
}
|
||||
|
||||
// 如果标签会超出右侧,向左调整
|
||||
var labelWidth = selectedLabel.offsetWidth || 100; // 预估宽度
|
||||
if (labelLeft + labelWidth > window.innerWidth - 10) {
|
||||
labelLeft = window.innerWidth - labelWidth - 10;
|
||||
}
|
||||
selectedLabel.style.left = "".concat(Math.max(5, labelLeft), "px");
|
||||
selectedLabel.style.top = "".concat(labelTop, "px");
|
||||
console.log("Selected highlight positioned at:", {
|
||||
left: selectedBox.style.left,
|
||||
top: selectedBox.style.top,
|
||||
width: selectedBox.style.width,
|
||||
height: selectedBox.style.height
|
||||
});
|
||||
}
|
||||
function getElementSelector(element) {
|
||||
if (!(element instanceof Element)) throw new Error('Argument must be a DOM element');
|
||||
var segments = [];
|
||||
var current = element;
|
||||
while (current !== document.documentElement) {
|
||||
var selector = '';
|
||||
// 优先检查唯一ID
|
||||
if (current.id && document.querySelectorAll("#".concat(current.id)).length === 1) {
|
||||
segments.unshift("#".concat(current.id));
|
||||
break; // ID唯一,无需继续向上
|
||||
}
|
||||
|
||||
// 生成类名选择器(取第一个有效类名)
|
||||
var classes = Array.from(current.classList).filter(function (c) {
|
||||
return !c.startsWith('js-');
|
||||
});
|
||||
var className = classes.length > 0 ? ".".concat(classes[0]) : '';
|
||||
|
||||
// 生成位置索引(nth-child)
|
||||
var tag = current.tagName.toLowerCase();
|
||||
if (!className) {
|
||||
var siblings = Array.from(current.parentNode.children);
|
||||
var index = siblings.findIndex(function (el) {
|
||||
return el === current;
|
||||
}) + 1;
|
||||
selector = "".concat(tag, ":nth-child(").concat(index, ")");
|
||||
} else {
|
||||
selector = className;
|
||||
}
|
||||
segments.unshift(selector);
|
||||
current = current.parentElement;
|
||||
}
|
||||
|
||||
// 处理根元素
|
||||
if (current === document.documentElement) {
|
||||
segments.unshift('html');
|
||||
}
|
||||
return segments.join(' > ');
|
||||
}
|
||||
|
||||
// 获取元素文本内容
|
||||
function getElementText(element) {
|
||||
var _element$textContent;
|
||||
if (element.tagName === "INPUT") {
|
||||
return element.value || element.placeholder || "";
|
||||
}
|
||||
if (element.tagName === "TEXTAREA") {
|
||||
return element.value || element.placeholder || "";
|
||||
}
|
||||
var text = ((_element$textContent = element.textContent) === null || _element$textContent === void 0 ? void 0 : _element$textContent.trim()) || "";
|
||||
return text.length > 50 ? text.substring(0, 50) + "..." : text;
|
||||
}
|
||||
|
||||
// 获取元素属性信息
|
||||
function getElementAttributes(element) {
|
||||
var attrs = {};
|
||||
for (var i = 0; i < element.attributes.length; i++) {
|
||||
var attr = element.attributes[i];
|
||||
attrs[attr.name] = attr.value;
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
// 鼠标悬停事件处理
|
||||
function handleMouseOver(e) {
|
||||
if (!highlightEnabled) return;
|
||||
var target = e.target;
|
||||
if (!target || target === overlay || target === highlightBox || target === tagLabel || target === selectedBox || target === selectedLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 避免高亮 html 和 body 元素
|
||||
if (target === document.documentElement || target === document.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是已选中的元素,不显示悬停高亮
|
||||
if (target === selectedElement) {
|
||||
highlightBox.style.display = "none";
|
||||
tagLabel.style.display = "none";
|
||||
return;
|
||||
}
|
||||
var rect = target.getBoundingClientRect();
|
||||
var selector = getElementSelector(target);
|
||||
var text = getElementText(target);
|
||||
var attributes = getElementAttributes(target);
|
||||
|
||||
// 更新悬停高亮框位置
|
||||
highlightBox.style.display = "block";
|
||||
highlightBox.style.left = "".concat(rect.left - 2, "px");
|
||||
highlightBox.style.top = "".concat(rect.top - 2, "px");
|
||||
highlightBox.style.width = "".concat(rect.width + 4, "px");
|
||||
highlightBox.style.height = "".concat(rect.height + 4, "px");
|
||||
|
||||
// 更新标签位置和内容
|
||||
tagLabel.style.display = "block";
|
||||
tagLabel.textContent = "<".concat(target.tagName.toLowerCase(), ">");
|
||||
|
||||
// 计算标签位置,确保不超出视窗
|
||||
var labelTop = rect.top - 22;
|
||||
var labelLeft = rect.left;
|
||||
|
||||
// 如果标签会超出顶部,显示在元素下方
|
||||
if (labelTop < 0) {
|
||||
labelTop = rect.bottom + 5;
|
||||
}
|
||||
|
||||
// 如果标签会超出右侧,向左调整
|
||||
if (labelLeft + tagLabel.offsetWidth > window.innerWidth) {
|
||||
labelLeft = window.innerWidth - tagLabel.offsetWidth - 5;
|
||||
}
|
||||
tagLabel.style.left = "".concat(Math.max(0, labelLeft), "px");
|
||||
tagLabel.style.top = "".concat(labelTop, "px");
|
||||
|
||||
// 发送消息到父窗口
|
||||
var elementInfo = {
|
||||
tagName: target.tagName.toLowerCase(),
|
||||
rect: {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
x: rect.x,
|
||||
y: rect.y
|
||||
},
|
||||
selector: selector,
|
||||
text: text,
|
||||
attributes: attributes,
|
||||
url: window.location.href,
|
||||
path: window.location.pathname,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: "iframe-element-hover",
|
||||
data: elementInfo,
|
||||
source: "iframe-highlight-injector"
|
||||
}, "*");
|
||||
} catch (error) {
|
||||
console.warn("无法发送消息到父窗口:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标离开事件处理
|
||||
function handleMouseOut(e) {
|
||||
if (!highlightEnabled) return;
|
||||
var relatedTarget = e.relatedTarget;
|
||||
|
||||
// 如果鼠标移动到高亮相关元素上,不隐藏高亮
|
||||
if (relatedTarget && (relatedTarget === highlightBox || relatedTarget === tagLabel || relatedTarget === overlay || relatedTarget === selectedBox || relatedTarget === selectedLabel)) {
|
||||
return;
|
||||
}
|
||||
highlightBox.style.display = "none";
|
||||
tagLabel.style.display = "none";
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: "iframe-element-hover",
|
||||
data: null,
|
||||
source: "iframe-highlight-injector"
|
||||
}, "*");
|
||||
} catch (error) {
|
||||
console.warn("无法发送消息到父窗口:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 点击事件处理
|
||||
function handleClick(e) {
|
||||
var target = e.target;
|
||||
if (!target || target === overlay || target === highlightBox || target === tagLabel || target === selectedBox || target === selectedLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 避免处理 html 和 body 元素
|
||||
if (target === document.documentElement || target === document.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是交互元素,这些元素需要保留默认行为
|
||||
var isInteractiveElement = ['input', 'textarea', 'select', 'button', 'a'].includes(target.tagName.toLowerCase());
|
||||
|
||||
// 如果高亮功能启用,对于非交互元素阻止默认行为和事件传播
|
||||
if (highlightEnabled) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
var rect = target.getBoundingClientRect();
|
||||
var selector = getElementSelector(target);
|
||||
var text = getElementText(target);
|
||||
var attributes = getElementAttributes(target);
|
||||
console.log("Element clicked:", {
|
||||
tagName: target.tagName,
|
||||
selector: selector,
|
||||
rect: rect
|
||||
});
|
||||
|
||||
// 立即更新选中高亮
|
||||
updateSelectedHighlight(target);
|
||||
|
||||
// 隐藏悬停高亮,因为现在是选中状态
|
||||
highlightBox.style.display = "none";
|
||||
tagLabel.style.display = "none";
|
||||
var elementInfo = {
|
||||
tagName: target.tagName.toLowerCase(),
|
||||
rect: {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
x: rect.x,
|
||||
y: rect.y
|
||||
},
|
||||
selector: selector,
|
||||
text: text,
|
||||
attributes: attributes,
|
||||
url: window.location.href,
|
||||
path: window.location.pathname,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: "iframe-element-click",
|
||||
data: elementInfo,
|
||||
source: "iframe-highlight-injector"
|
||||
}, "*");
|
||||
} catch (error) {
|
||||
console.warn("无法发送消息到父窗口:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 监听来自父窗口的消息
|
||||
function handleParentMessage(event) {
|
||||
console.log("Received message from parent:", event.data);
|
||||
if (event.data.type === "iframe-highlight-toggle") {
|
||||
var enabled = event.data.enabled;
|
||||
console.log("Highlight toggle:", enabled);
|
||||
if (enabled) {
|
||||
enableHighlight();
|
||||
} else {
|
||||
disableHighlight();
|
||||
}
|
||||
} else if (event.data.type === "enable-iframe-highlight") {
|
||||
console.log("Enable iframe highlight");
|
||||
enableHighlight();
|
||||
} else if (event.data.type === "disable-iframe-highlight") {
|
||||
console.log("Disable iframe highlight");
|
||||
disableHighlight();
|
||||
} else if (event.data.type === "toggle-iframe-highlight") {
|
||||
var _enabled = event.data.enabled !== undefined ? event.data.enabled : !highlightEnabled;
|
||||
console.log("Toggle iframe highlight to:", _enabled);
|
||||
if (_enabled) {
|
||||
enableHighlight();
|
||||
} else {
|
||||
disableHighlight();
|
||||
}
|
||||
} else if (event.data.type === "update-selected-element") {
|
||||
var selector = event.data.selector;
|
||||
console.log("Update selected element with selector:", selector);
|
||||
if (selector) {
|
||||
try {
|
||||
var element = document.querySelector(selector);
|
||||
console.log("Found element by selector:", element);
|
||||
updateSelectedHighlight(element);
|
||||
} catch (error) {
|
||||
console.warn("Failed to select element:", error);
|
||||
updateSelectedHighlight(null);
|
||||
}
|
||||
} else {
|
||||
updateSelectedHighlight(null);
|
||||
}
|
||||
} else if (event.data.type === "clear-selected-element") {
|
||||
console.log("Clear selected element");
|
||||
updateSelectedHighlight(null);
|
||||
}
|
||||
}
|
||||
|
||||
// 启用高亮功能
|
||||
function enableHighlight() {
|
||||
console.log("Enabling highlight");
|
||||
document.addEventListener("mouseover", handleMouseOver, true);
|
||||
document.addEventListener("mouseout", handleMouseOut, true);
|
||||
document.addEventListener("click", handleClick, true);
|
||||
highlightEnabled = true;
|
||||
overlay.style.display = "block";
|
||||
}
|
||||
|
||||
// 禁用高亮功能
|
||||
function disableHighlight() {
|
||||
console.log("Disabling highlight");
|
||||
highlightEnabled = false;
|
||||
// 保持事件监听器,但通过 highlightEnabled 变量控制行为
|
||||
// 这样可以保留选中状态的显示
|
||||
highlightBox.style.display = "none";
|
||||
tagLabel.style.display = "none";
|
||||
// 不隐藏 selectedBox 和 selectedLabel,保留选中状态
|
||||
}
|
||||
|
||||
// 完全禁用高亮功能(移除所有监听器)
|
||||
function fullyDisableHighlight() {
|
||||
console.log("Fully disabling highlight");
|
||||
highlightEnabled = false;
|
||||
document.removeEventListener("mouseover", handleMouseOver, true);
|
||||
document.removeEventListener("mouseout", handleMouseOut, true);
|
||||
document.removeEventListener("click", handleClick, true);
|
||||
overlay.style.display = "none";
|
||||
highlightBox.style.display = "none";
|
||||
tagLabel.style.display = "none";
|
||||
selectedBox.style.display = "none";
|
||||
selectedLabel.style.display = "none";
|
||||
}
|
||||
|
||||
// 添加事件监听
|
||||
enableHighlight();
|
||||
window.addEventListener("message", handleParentMessage);
|
||||
|
||||
// 暴露全局函数供外部调用
|
||||
window.__iframeHighlightControl = {
|
||||
enable: enableHighlight,
|
||||
disable: disableHighlight,
|
||||
fullyDisable: fullyDisableHighlight,
|
||||
isEnabled: function isEnabled() {
|
||||
return highlightEnabled;
|
||||
},
|
||||
getSelectedElement: function getSelectedElement() {
|
||||
return selectedElement;
|
||||
},
|
||||
updateSelected: updateSelectedHighlight,
|
||||
// 通过消息发送开关控制
|
||||
sendToggleMessage: function sendToggleMessage(enabled) {
|
||||
window.parent.postMessage({
|
||||
type: 'iframe-highlight-status',
|
||||
enabled: enabled || highlightEnabled,
|
||||
source: 'iframe-highlight-injector'
|
||||
}, '*');
|
||||
}
|
||||
};
|
||||
|
||||
// 通知父窗口脚本已加载
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: "iframe-highlight-ready",
|
||||
data: {
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
source: "iframe-highlight-injector"
|
||||
}, "*");
|
||||
} catch (error) {
|
||||
console.warn("无法发送就绪消息到父窗口:", error);
|
||||
}
|
||||
|
||||
// 清理函数
|
||||
window.__iframeHighlightCleanup = function () {
|
||||
fullyDisableHighlight();
|
||||
window.removeEventListener("message", handleParentMessage);
|
||||
if (overlay.parentElement) {
|
||||
overlay.parentElement.removeChild(overlay);
|
||||
}
|
||||
delete window.__iframeHighlightInitialized;
|
||||
delete window.__iframeHighlightCleanup;
|
||||
};
|
||||
})();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// Analog Clock JavaScript
|
||||
class AnalogClock {
|
||||
constructor() {
|
||||
this.hourHand = document.getElementById('hour-hand');
|
||||
this.minuteHand = document.getElementById('minute-hand');
|
||||
this.secondHand = document.getElementById('second-hand');
|
||||
|
||||
// Initialize clock
|
||||
this.updateClock();
|
||||
|
||||
// Update every second
|
||||
setInterval(() => {
|
||||
this.updateClock();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
updateClock() {
|
||||
const now = new Date();
|
||||
|
||||
// Get current time components
|
||||
const hours = now.getHours() % 12; // Convert to 12-hour format
|
||||
const minutes = now.getMinutes();
|
||||
const seconds = now.getSeconds();
|
||||
|
||||
// Calculate rotation angles
|
||||
const hourAngle = (hours * 30) + (minutes * 0.5); // 30 degrees per hour + minute adjustment
|
||||
const minuteAngle = (minutes * 6) + (seconds * 0.1); // 6 degrees per minute + second adjustment
|
||||
const secondAngle = seconds * 6; // 6 degrees per second
|
||||
|
||||
// Apply rotations to hands
|
||||
this.hourHand.style.transform = `translateX(-50%) rotate(${hourAngle}deg)`;
|
||||
this.minuteHand.style.transform = `translateX(-50%) rotate(${minuteAngle}deg)`;
|
||||
this.secondHand.style.transform = `translateX(-50%) rotate(${secondAngle}deg)`;
|
||||
|
||||
// Store current rotation for smooth animation
|
||||
this.hourHand.style.setProperty('--current-rotation', `${hourAngle}deg`);
|
||||
this.minuteHand.style.setProperty('--current-rotation', `${minuteAngle}deg`);
|
||||
this.secondHand.style.setProperty('--current-rotation', `${secondAngle}deg`);
|
||||
}
|
||||
|
||||
// Method to handle window resize for better responsiveness
|
||||
handleResize() {
|
||||
const clock = document.querySelector('.clock');
|
||||
const container = document.querySelector('.clock-container');
|
||||
|
||||
// Ensure clock stays centered and properly sized
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const maxSize = Math.min(containerRect.width - 40, containerRect.height - 40);
|
||||
const clockSize = Math.max(250, Math.min(400, maxSize));
|
||||
|
||||
clock.style.width = `${clockSize}px`;
|
||||
clock.style.height = `${clockSize}px`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize clock when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const clock = new AnalogClock();
|
||||
|
||||
// Handle window resize for responsiveness
|
||||
let resizeTimeout;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(() => {
|
||||
clock.handleResize();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Initial resize adjustment
|
||||
clock.handleResize();
|
||||
});
|
||||
|
||||
// Optional: Add smooth animation for second hand
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const secondHand = document.getElementById('second-hand');
|
||||
|
||||
// Enhanced smooth animation for second hand
|
||||
const enhanceSecondHandAnimation = () => {
|
||||
const now = new Date();
|
||||
const milliseconds = now.getMilliseconds();
|
||||
const currentSeconds = now.getSeconds();
|
||||
const fractionalSeconds = currentSeconds + (milliseconds / 1000);
|
||||
|
||||
const smoothSecondAngle = fractionalSeconds * 6; // 6 degrees per second
|
||||
|
||||
// Use CSS transform for smoother animation
|
||||
secondHand.style.transition = `transform 0.1s cubic-bezier(0.4, 0.0, 0.2, 1)`;
|
||||
secondHand.style.transform = `translateX(-50%) rotate(${smoothSecondAngle}deg)`;
|
||||
|
||||
requestAnimationFrame(enhanceSecondHandAnimation);
|
||||
};
|
||||
|
||||
// Start smooth animation
|
||||
requestAnimationFrame(enhanceSecondHandAnimation);
|
||||
});
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
/* Reset and base styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background-color: #ffffff;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Clock container */
|
||||
.clock-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Main clock face */
|
||||
.clock {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
background-color: #f0f0f0;
|
||||
border: 8px solid #333333;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Numbers positioning */
|
||||
.number {
|
||||
position: absolute;
|
||||
color: #000000;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.number span {
|
||||
display: block;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.number-1 { top: 15%; left: 75%; transform: translate(-50%, -50%); }
|
||||
.number-2 { top: 27%; left: 90%; transform: translate(-50%, -50%); }
|
||||
.number-3 { top: 50%; left: 95%; transform: translate(-50%, -50%); }
|
||||
.number-4 { top: 73%; left: 90%; transform: translate(-50%, -50%); }
|
||||
.number-5 { top: 85%; left: 75%; transform: translate(-50%, -50%); }
|
||||
.number-6 { top: 90%; left: 50%; transform: translate(-50%, -50%); }
|
||||
.number-7 { top: 85%; left: 25%; transform: translate(-50%, -50%); }
|
||||
.number-8 { top: 73%; left: 10%; transform: translate(-50%, -50%); }
|
||||
.number-9 { top: 50%; left: 5%; transform: translate(-50%, -50%); }
|
||||
.number-10 { top: 27%; left: 10%; transform: translate(-50%, -50%); }
|
||||
.number-11 { top: 15%; left: 25%; transform: translate(-50%, -50%); }
|
||||
.number-12 { top: 10%; left: 50%; transform: translate(-50%, -50%); }
|
||||
|
||||
/* Clock hands */
|
||||
.hand {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
left: 50%;
|
||||
transform-origin: bottom center;
|
||||
transform: translateX(-50%) rotate(0deg);
|
||||
transition: transform 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.hour-hand {
|
||||
width: 6px;
|
||||
height: 80px;
|
||||
background-color: #000000;
|
||||
border-radius: 3px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.minute-hand {
|
||||
width: 4px;
|
||||
height: 120px;
|
||||
background-color: #000000;
|
||||
border-radius: 2px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.second-hand {
|
||||
width: 2px;
|
||||
height: 140px;
|
||||
background-color: #ff0000;
|
||||
border-radius: 1px;
|
||||
z-index: 3;
|
||||
transition: transform 0.1s cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Center pivot */
|
||||
.center-pivot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: #333333;
|
||||
border: 2px solid #ffffff;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.clock {
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.hour-hand {
|
||||
height: 75px;
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.minute-hand {
|
||||
height: 110px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.second-hand {
|
||||
height: 130px;
|
||||
width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.clock {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-width: 6px;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hour-hand {
|
||||
height: 65px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.minute-hand {
|
||||
height: 95px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.second-hand {
|
||||
height: 115px;
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
.center-pivot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for smooth rotation */
|
||||
@keyframes smooth-rotation {
|
||||
from {
|
||||
transform: translateX(-50%) rotate(var(--current-rotation, 0deg));
|
||||
}
|
||||
to {
|
||||
transform: translateX(-50%) rotate(calc(var(--current-rotation, 0deg) + 360deg));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Load the clock page
|
||||
await page.goto('file:///workspace/index.html');
|
||||
|
||||
// Wait for the clock to load and render
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Test 1: Check if clock container exists
|
||||
const clockContainer = await page.$('.clock-container');
|
||||
console.log('✓ Clock container found:', !!clockContainer);
|
||||
|
||||
// Test 2: Check if all clock hands exist
|
||||
const hourHand = await page.$('#hour-hand');
|
||||
const minuteHand = await page.$('#minute-hand');
|
||||
const secondHand = await page.$('#second-hand');
|
||||
console.log('✓ Hour hand found:', !!hourHand);
|
||||
console.log('✓ Minute hand found:', !!minuteHand);
|
||||
console.log('✓ Second hand found:', !!secondHand);
|
||||
|
||||
// Test 3: Check if all numbers exist
|
||||
const numbers = await page.$$('.number');
|
||||
console.log('✓ Numbers found:', numbers.length, '(expected: 12)');
|
||||
|
||||
// Test 4: Check background color
|
||||
const bodyBgColor = await page.evaluate(() => {
|
||||
return window.getComputedStyle(document.body).backgroundColor;
|
||||
});
|
||||
console.log('✓ Background color:', bodyBgColor, '(expected: rgb(255, 255, 255))');
|
||||
|
||||
// Test 5: Check if hands have proper styling
|
||||
const secondHandStyles = await page.evaluate(() => {
|
||||
const hand = document.getElementById('second-hand');
|
||||
const styles = window.getComputedStyle(hand);
|
||||
return {
|
||||
position: styles.position,
|
||||
transform: styles.transform,
|
||||
backgroundColor: styles.backgroundColor,
|
||||
transition: styles.transition
|
||||
};
|
||||
});
|
||||
console.log('✓ Second hand styles:', JSON.stringify(secondHandStyles, null, 2));
|
||||
|
||||
// Test 6: Check if the clock updates (get initial time)
|
||||
const initialTime = await page.evaluate(() => {
|
||||
const now = new Date();
|
||||
return {
|
||||
hours: now.getHours() % 12,
|
||||
minutes: now.getMinutes(),
|
||||
seconds: now.getSeconds()
|
||||
};
|
||||
});
|
||||
console.log('✓ Initial time:', initialTime);
|
||||
|
||||
// Wait 3 seconds and check if time updates
|
||||
await page.waitForTimeout(3000);
|
||||
const updatedTime = await page.evaluate(() => {
|
||||
const now = new Date();
|
||||
return {
|
||||
hours: now.getHours() % 12,
|
||||
minutes: now.getMinutes(),
|
||||
seconds: now.getSeconds()
|
||||
};
|
||||
});
|
||||
console.log('✓ Updated time:', updatedTime);
|
||||
|
||||
// Test 7: Check responsiveness by simulating different screen sizes
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.waitForTimeout(1000);
|
||||
const clockSizeMobile = await page.evaluate(() => {
|
||||
const clock = document.querySelector('.clock');
|
||||
return {
|
||||
width: clock.offsetWidth,
|
||||
height: clock.offsetHeight
|
||||
};
|
||||
});
|
||||
console.log('✓ Mobile size (375px width):', clockSizeMobile);
|
||||
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await page.waitForTimeout(1000);
|
||||
const clockSizeDesktop = await page.evaluate(() => {
|
||||
const clock = document.querySelector('.clock');
|
||||
return {
|
||||
width: clock.offsetWidth,
|
||||
height: clock.offsetHeight
|
||||
};
|
||||
});
|
||||
console.log('✓ Desktop size (1920px width):', clockSizeDesktop);
|
||||
|
||||
await browser.close();
|
||||
console.log('\n✅ All tests passed! Analog clock is working correctly.');
|
||||
})();
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
SELECT
|
||||
qu.id AS qy_user_id,
|
||||
qu.name AS user_name,
|
||||
qu.ab98_user_id,
|
||||
qu.corpid,
|
||||
qu.balance AS qy_balance_yuan,
|
||||
qu.use_balance AS qy_use_balance_yuan,
|
||||
qu.balance_limit AS qy_balance_limit_yuan,
|
||||
ub.balance AS ub_balance_fen,
|
||||
ub.use_balance AS ub_use_balance_fen,
|
||||
ub.balance_limit AS ub_balance_limit_fen,
|
||||
ROUND(qu.balance * 100) AS qy_balance_converted_fen,
|
||||
ROUND(qu.use_balance * 100) AS qy_use_balance_converted_fen,
|
||||
ROUND(qu.balance_limit * 100) AS qy_balance_limit_converted_fen
|
||||
FROM qy_user qu
|
||||
INNER JOIN user_balance ub ON (
|
||||
(qu.ab98_user_id IS NOT NULL AND ub.ab98_user_id = qu.ab98_user_id)
|
||||
OR
|
||||
(qu.ab98_user_id IS NULL AND ub.qy_user_id = qu.id)
|
||||
)
|
||||
WHERE qu.deleted = 0
|
||||
AND ub.deleted = 0
|
||||
AND (
|
||||
ROUND(qu.balance * 100) != ub.balance
|
||||
OR ROUND(qu.use_balance * 100) != ub.use_balance
|
||||
OR ROUND(qu.balance_limit * 100) != ub.balance_limit
|
||||
);
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
CREATE TABLE `qy_user` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键编号',
|
||||
`oper_id` int DEFAULT NULL COMMENT '操作序号',
|
||||
`open_userid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '全局唯一',
|
||||
`userid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '成员UserID。对应管理端的账号,企业内必须唯一。不区分大小写',
|
||||
`ab98_user_id` bigint DEFAULT NULL COMMENT '汇邦云用户id',
|
||||
`name` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '成员名称',
|
||||
`mobile` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号码',
|
||||
`department` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '成员所属部门id列表',
|
||||
`user_order` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门内的排序值,默认为0',
|
||||
`position` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '职务信息',
|
||||
`gender` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '性别。0表示未定义,1表示男性,2表示女性。',
|
||||
`email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
|
||||
`biz_mail` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '企业邮箱',
|
||||
`is_leader_in_dept` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '表示在所在的部门内是否为部门负责人,数量与department一致',
|
||||
`direct_leader` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '直属上级UserID',
|
||||
`avatar` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`thumb_avatar` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像缩略图url',
|
||||
`telephone` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '座机',
|
||||
`alias` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '别名',
|
||||
`extattr` varchar(300) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '扩展属性',
|
||||
`status` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业。',
|
||||
`qr_code` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '员工个人二维码',
|
||||
`external_profile` varchar(300) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '成员对外属性',
|
||||
`external_position` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '对外职务',
|
||||
`address` varchar(300) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '地址',
|
||||
`main_department` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '主部门,仅当应用对主部门有查看权限时返回。',
|
||||
`enable` varchar(2) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '有效标志(1-有效,0-无效)',
|
||||
`corpid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '企业微信id',
|
||||
`appid` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '应用ID',
|
||||
`cid` int DEFAULT NULL COMMENT '公司ID',
|
||||
`creator_id` bigint NOT NULL DEFAULT '0' COMMENT '创建者ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` bigint NOT NULL DEFAULT '0' COMMENT '更新者ID',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
|
||||
`balance` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '用户余额(精度与商品价格对齐)',
|
||||
`use_balance` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '已使用用户余额',
|
||||
`balance_limit` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '余额额度',
|
||||
`sys_role_id` bigint DEFAULT NULL COMMENT '系统角色id',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `ik_qy_user_cid` (`corpid`),
|
||||
KEY `ik_qy_user_userid` (`userid`),
|
||||
KEY `ik_qy_user_mobile` (`mobile`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=831 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='企业微信-人员信息';
|
||||
|
||||
CREATE TABLE `ab98_user` (
|
||||
`ab98_user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`openid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'openid',
|
||||
`userid` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '汇邦云用户唯一ID',
|
||||
`qy_user_id` bigint DEFAULT NULL COMMENT '企业用户id',
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '真实姓名',
|
||||
`tel` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号码',
|
||||
`idnum` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '身份证号码',
|
||||
`sex` char(8) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '性别(男 女)',
|
||||
`face_img` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '人脸照片地址',
|
||||
`idcard_front` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证正面地址',
|
||||
`idcard_back` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证背面地址',
|
||||
`address` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证登记地址',
|
||||
`registered` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已注册(0未注册 1已注册)',
|
||||
`creator_id` bigint DEFAULT '0' COMMENT '创建者ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` bigint DEFAULT '0' COMMENT '更新者ID',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
|
||||
`ab98_balance` int NOT NULL DEFAULT '0' COMMENT '用户余额(单位:分)',
|
||||
PRIMARY KEY (`ab98_user_id`),
|
||||
KEY `idx_openid` (`openid`),
|
||||
KEY `idx_tel` (`tel`),
|
||||
KEY `idx_name` (`name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='汇邦云用户信息表';
|
||||
|
||||
CREATE TABLE `user_balance` (
|
||||
`user_balance_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`corpid` varchar(50) NOT NULL COMMENT '企业微信id',
|
||||
`openid` varchar(32) DEFAULT NULL COMMENT 'openid',
|
||||
`ab98_user_id` bigint NOT NULL COMMENT '汇邦云用户ID',
|
||||
`qy_user_id` bigint DEFAULT NULL COMMENT '企业用户id',
|
||||
`balance` bigint NOT NULL DEFAULT 0 COMMENT '用户余额(单位:分)',
|
||||
`use_balance` bigint NOT NULL DEFAULT 0 COMMENT '用户余额(单位:分)',
|
||||
`balance_limit` bigint NOT NULL DEFAULT 0 COMMENT '用户余额(单位:分)',
|
||||
`creator_id` bigint NULL DEFAULT 0 COMMENT '创建者ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` bigint NULL DEFAULT 0 COMMENT '更新者ID',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
|
||||
PRIMARY KEY (`user_balance_id`),
|
||||
KEY `idx_openid` (`openid`),
|
||||
KEY `idx_ab98_user_id` (`ab98_user_id`),
|
||||
KEY `idx_corpid` (`corpid`),
|
||||
UNIQUE KEY `uk_corpid_user` (`corpid`, `ab98_user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户余额表';
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,95 @@
|
|||
CREATE TABLE `qy_user` (
|
||||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键编号',
|
||||
`oper_id` int DEFAULT NULL COMMENT '操作序号',
|
||||
`open_userid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '全局唯一',
|
||||
`userid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '成员UserID。对应管理端的账号,企业内必须唯一。不区分大小写',
|
||||
`ab98_user_id` bigint DEFAULT NULL COMMENT '汇邦云用户id',
|
||||
`name` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '成员名称',
|
||||
`mobile` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号码',
|
||||
`department` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '成员所属部门id列表',
|
||||
`user_order` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门内的排序值,默认为0',
|
||||
`position` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '职务信息',
|
||||
`gender` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '性别。0表示未定义,1表示男性,2表示女性。',
|
||||
`email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
|
||||
`biz_mail` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '企业邮箱',
|
||||
`is_leader_in_dept` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '表示在所在的部门内是否为部门负责人,数量与department一致',
|
||||
`direct_leader` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '直属上级UserID',
|
||||
`avatar` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`thumb_avatar` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像缩略图url',
|
||||
`telephone` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '座机',
|
||||
`alias` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '别名',
|
||||
`extattr` varchar(300) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '扩展属性',
|
||||
`status` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '激活状态: 1=已激活,2=已禁用,4=未激活,5=退出企业。',
|
||||
`qr_code` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '员工个人二维码',
|
||||
`external_profile` varchar(300) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '成员对外属性',
|
||||
`external_position` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '对外职务',
|
||||
`address` varchar(300) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '地址',
|
||||
`main_department` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '主部门,仅当应用对主部门有查看权限时返回。',
|
||||
`enable` varchar(2) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '有效标志(1-有效,0-无效)',
|
||||
`corpid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '企业微信id',
|
||||
`appid` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '应用ID',
|
||||
`cid` int DEFAULT NULL COMMENT '公司ID',
|
||||
`creator_id` bigint NOT NULL DEFAULT '0' COMMENT '创建者ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` bigint NOT NULL DEFAULT '0' COMMENT '更新者ID',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
|
||||
`balance` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '用户余额(精度与商品价格对齐)',
|
||||
`use_balance` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '已使用用户余额',
|
||||
`balance_limit` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '余额额度',
|
||||
`sys_role_id` bigint DEFAULT NULL COMMENT '系统角色id',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `ik_qy_user_cid` (`corpid`),
|
||||
KEY `ik_qy_user_userid` (`userid`),
|
||||
KEY `ik_qy_user_mobile` (`mobile`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=831 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='企业微信-人员信息';
|
||||
|
||||
CREATE TABLE `ab98_user` (
|
||||
`ab98_user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`openid` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'openid',
|
||||
`userid` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '汇邦云用户唯一ID',
|
||||
`qy_user_id` bigint DEFAULT NULL COMMENT '企业用户id',
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '真实姓名',
|
||||
`tel` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号码',
|
||||
`idnum` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '身份证号码',
|
||||
`sex` char(8) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '性别(男 女)',
|
||||
`face_img` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '人脸照片地址',
|
||||
`idcard_front` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证正面地址',
|
||||
`idcard_back` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证背面地址',
|
||||
`address` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '身份证登记地址',
|
||||
`registered` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已注册(0未注册 1已注册)',
|
||||
`creator_id` bigint DEFAULT '0' COMMENT '创建者ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` bigint DEFAULT '0' COMMENT '更新者ID',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
|
||||
`ab98_balance` int NOT NULL DEFAULT '0' COMMENT '用户余额(单位:分)',
|
||||
PRIMARY KEY (`ab98_user_id`),
|
||||
KEY `idx_openid` (`openid`),
|
||||
KEY `idx_tel` (`tel`),
|
||||
KEY `idx_name` (`name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='汇邦云用户信息表';
|
||||
|
||||
CREATE TABLE `user_balance` (
|
||||
`user_balance_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`corpid` varchar(50) NOT NULL COMMENT '企业微信id',
|
||||
`openid` varchar(32) DEFAULT NULL COMMENT 'openid',
|
||||
`ab98_user_id` bigint NOT NULL COMMENT '汇邦云用户ID',
|
||||
`qy_user_id` bigint DEFAULT NULL COMMENT '企业用户id',
|
||||
`balance` bigint NOT NULL DEFAULT 0 COMMENT '用户余额(单位:分)',
|
||||
`use_balance` bigint NOT NULL DEFAULT 0 COMMENT '用户余额(单位:分)',
|
||||
`balance_limit` bigint NOT NULL DEFAULT 0 COMMENT '用户余额(单位:分)',
|
||||
`creator_id` bigint NULL DEFAULT 0 COMMENT '创建者ID',
|
||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater_id` bigint NULL DEFAULT 0 COMMENT '更新者ID',
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)',
|
||||
PRIMARY KEY (`user_balance_id`),
|
||||
KEY `idx_openid` (`openid`),
|
||||
KEY `idx_ab98_user_id` (`ab98_user_id`),
|
||||
KEY `idx_corpid` (`corpid`),
|
||||
UNIQUE KEY `uk_corpid_user` (`corpid`, `ab98_user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户余额表';
|
||||
|
||||
|
||||
user_balance是最近新增的表,写一个sql脚本,把qy_user中的balance、use_balance、balance_limit更新到user_balance中,注意原来的字段类型是decimal(15,2)单位为元,现在是整数类型单位是分。qy_user里ab98_user_id字段不为空的通过ab98_user_id进行关联,qy_user里ab98_user_id字段为空的通过name进行关联。user_balance和ab98_user已经关联完成了。脚本可以对ab98_user_id是否为空分步进行
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
我来设计一个具有复杂关联关系和迷惑项的电商数据库模型:
|
||||
|
||||
## 数据库:E-Commerce System
|
||||
|
||||
### 表设计(包含迷惑项)
|
||||
|
||||
#### 1. 用户相关表
|
||||
```sql
|
||||
-- 用户主表(与客户表名称相似,容易混淆)
|
||||
CREATE TABLE users (
|
||||
user_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
phone_number VARCHAR(20),
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
status ENUM('active', 'inactive', 'suspended') DEFAULT 'active'
|
||||
);
|
||||
|
||||
-- 客户信息表(与用户表相似,但有不同用途)
|
||||
CREATE TABLE customers (
|
||||
customer_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL, -- 外键,但名称与users表主键不完全匹配
|
||||
full_name VARCHAR(100),
|
||||
birth_date DATE,
|
||||
registration_date DATE,
|
||||
customer_type ENUM('individual', 'business', 'vip') DEFAULT 'individual',
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) -- 故意写错:应该是user_id REFERENCES users(user_id)
|
||||
);
|
||||
|
||||
-- 用户地址表
|
||||
CREATE TABLE user_addresses (
|
||||
address_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
customer_id INT, -- 冗余字段,增加复杂度
|
||||
address_line1 VARCHAR(200),
|
||||
address_line2 VARCHAR(200),
|
||||
city_id INT,
|
||||
postal_code VARCHAR(20),
|
||||
is_default BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
||||
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### 2. 商品相关表
|
||||
```sql
|
||||
-- 商品表
|
||||
CREATE TABLE products (
|
||||
product_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_code VARCHAR(50) UNIQUE NOT NULL, -- 与sku相似
|
||||
product_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
cost_price DECIMAL(10,2), -- 与price相似
|
||||
category_id INT,
|
||||
supplier_id INT,
|
||||
stock_quantity INT DEFAULT 0,
|
||||
reorder_level INT DEFAULT 10,
|
||||
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
modified_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 产品表(名称与products相似,但内容不同)
|
||||
CREATE TABLE items (
|
||||
item_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
sku VARCHAR(50) UNIQUE, -- 与product_code相似
|
||||
variant_name VARCHAR(100),
|
||||
additional_price DECIMAL(10,2) DEFAULT 0,
|
||||
inventory_count INT DEFAULT 0,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
);
|
||||
|
||||
-- 商品分类表
|
||||
CREATE TABLE categories (
|
||||
category_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
category_name VARCHAR(100) NOT NULL,
|
||||
parent_category_id INT, -- 自引用,实现层级分类
|
||||
category_path VARCHAR(500),
|
||||
FOREIGN KEY (parent_category_id) REFERENCES categories(category_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### 3. 订单相关表
|
||||
```sql
|
||||
-- 订单主表
|
||||
CREATE TABLE orders (
|
||||
order_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_number VARCHAR(50) UNIQUE NOT NULL,
|
||||
customer_id INT NOT NULL, -- 注意:这里引用customers表,不是users表
|
||||
user_id INT, -- 冗余字段
|
||||
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
order_status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
|
||||
total_amount DECIMAL(10,2),
|
||||
net_amount DECIMAL(10,2), -- 与total_amount相似
|
||||
shipping_address_id INT,
|
||||
billing_address_id INT,
|
||||
payment_method VARCHAR(50)
|
||||
);
|
||||
|
||||
-- 订单项表
|
||||
CREATE TABLE order_items (
|
||||
order_item_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
item_id INT, -- 引用items表
|
||||
product_id INT, -- 也引用products表,增加复杂度
|
||||
quantity INT NOT NULL,
|
||||
unit_price DECIMAL(10,2) NOT NULL,
|
||||
discount DECIMAL(10,2) DEFAULT 0,
|
||||
subtotal DECIMAL(10,2),
|
||||
FOREIGN KEY (order_id) REFERENCES orders(order_id),
|
||||
FOREIGN KEY (item_id) REFERENCES items(item_id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
);
|
||||
|
||||
-- 发货表
|
||||
CREATE TABLE shipments (
|
||||
shipment_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
shipment_date DATE,
|
||||
estimated_delivery DATE, -- 与delivery_date相似
|
||||
actual_delivery DATE, -- 与estimated_delivery相似
|
||||
carrier VARCHAR(100),
|
||||
tracking_number VARCHAR(100),
|
||||
status ENUM('preparing', 'shipped', 'in_transit', 'delivered') DEFAULT 'preparing'
|
||||
);
|
||||
```
|
||||
|
||||
#### 4. 多对多关系表
|
||||
```sql
|
||||
-- 商品-供应商关系(多对多)
|
||||
CREATE TABLE product_suppliers (
|
||||
product_id INT NOT NULL,
|
||||
supplier_id INT NOT NULL,
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
purchase_price DECIMAL(10,2),
|
||||
PRIMARY KEY (product_id, supplier_id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
-- 故意不创建supplier_id的外键约束,增加测试难度
|
||||
);
|
||||
|
||||
-- 用户-收藏夹关系(多对多)
|
||||
CREATE TABLE user_wishlists (
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
added_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
priority INT DEFAULT 1,
|
||||
PRIMARY KEY (user_id, product_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### 5. 其他相关表(增加迷惑性)
|
||||
```sql
|
||||
-- 支付表
|
||||
CREATE TABLE payments (
|
||||
payment_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
payment_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
amount DECIMAL(10,2),
|
||||
payment_type VARCHAR(50), -- 与payment_method相似
|
||||
transaction_id VARCHAR(100),
|
||||
payment_status ENUM('pending', 'completed', 'failed', 'refunded')
|
||||
);
|
||||
|
||||
-- 评价表
|
||||
CREATE TABLE reviews (
|
||||
review_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
rating INT CHECK (rating >= 1 AND rating <= 5),
|
||||
comment TEXT,
|
||||
review_date DATE, -- 与created_at相似但不同
|
||||
helpful_count INT DEFAULT 0
|
||||
);
|
||||
|
||||
-- 促销表
|
||||
CREATE TABLE promotions (
|
||||
promo_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
promo_code VARCHAR(50) UNIQUE,
|
||||
promo_name VARCHAR(100),
|
||||
discount_type ENUM('percentage', 'fixed', 'buy_one_get_one'),
|
||||
discount_value DECIMAL(10,2),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
minimum_purchase DECIMAL(10,2)
|
||||
);
|
||||
```
|
||||
|
||||
### 迷惑项总结:
|
||||
1. **相似表名**:products/items, users/customers
|
||||
2. **相似字段名**:price/cost_price, total_amount/net_amount, estimated_delivery/actual_delivery
|
||||
3. **含义相近字段**:created_date/created_at/created_time, payment_type/payment_method
|
||||
4. **冗余字段**:order_items表中同时有item_id和product_id
|
||||
5. **不一致的命名**:user_id vs customer_id在不同表的引用
|
||||
6. **缺失的外键**:product_suppliers表中的supplier_id没有外键约束
|
||||
7. **相似的用途不同名**:product_code vs sku
|
||||
8. **同义词字段**:stock_quantity vs inventory_count
|
||||
|
||||
### 测试查询示例:
|
||||
```sql
|
||||
-- 复杂查询1:找出每个用户最常购买的商品类别
|
||||
-- 需要连接:users → customers → orders → order_items → products → categories
|
||||
|
||||
-- 复杂查询2:计算每个供应商的商品在最近一个月的销售额
|
||||
-- 需要连接:product_suppliers → products → order_items → orders
|
||||
|
||||
-- 复杂查询3:找出有相同商品的用户对(社交推荐)
|
||||
-- 需要多对多关系分析:users → user_wishlists → products ← user_wishlists ← users
|
||||
|
||||
-- 复杂查询4:层级分类的商品统计(递归查询)
|
||||
-- 需要使用WITH RECURSIVE处理categories表的层级关系
|
||||
```
|
||||
|
||||
这个设计包含了:
|
||||
- 一对多关系(用户-地址、订单-订单项)
|
||||
- 多对多关系(用户-收藏夹、商品-供应商)
|
||||
- 自引用关系(分类层级)
|
||||
- 复杂的业务逻辑和数据冗余
|
||||
- 各种迷惑项和陷阱
|
||||
|
||||
你可以用这个数据库模型来测试LLM对复杂SQL查询的理解和生成能力。
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
CREATE TABLE users (
|
||||
user_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
phone_number VARCHAR(20),
|
||||
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
status ENUM('active', 'inactive', 'suspended') DEFAULT 'active'
|
||||
);
|
||||
|
||||
CREATE TABLE customers (
|
||||
customer_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
full_name VARCHAR(100),
|
||||
birth_date DATE,
|
||||
registration_date DATE,
|
||||
customer_type ENUM('individual', 'business', 'vip') DEFAULT 'individual',
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE user_addresses (
|
||||
address_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
customer_id INT,
|
||||
address_line1 VARCHAR(200),
|
||||
address_line2 VARCHAR(200),
|
||||
city_id INT,
|
||||
postal_code VARCHAR(20),
|
||||
is_default BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
||||
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
|
||||
);
|
||||
|
||||
CREATE TABLE products (
|
||||
product_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_code VARCHAR(50) UNIQUE NOT NULL,
|
||||
product_name VARCHAR(200) NOT NULL,
|
||||
description TEXT,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
cost_price DECIMAL(10,2),
|
||||
category_id INT,
|
||||
supplier_id INT,
|
||||
stock_quantity INT DEFAULT 0,
|
||||
reorder_level INT DEFAULT 10,
|
||||
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
modified_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE items (
|
||||
item_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
sku VARCHAR(50) UNIQUE,
|
||||
variant_name VARCHAR(100),
|
||||
additional_price DECIMAL(10,2) DEFAULT 0,
|
||||
inventory_count INT DEFAULT 0,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
);
|
||||
|
||||
CREATE TABLE categories (
|
||||
category_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
category_name VARCHAR(100) NOT NULL,
|
||||
parent_category_id INT,
|
||||
category_path VARCHAR(500),
|
||||
FOREIGN KEY (parent_category_id) REFERENCES categories(category_id)
|
||||
);
|
||||
|
||||
CREATE TABLE orders (
|
||||
order_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_number VARCHAR(50) UNIQUE NOT NULL,
|
||||
customer_id INT NOT NULL,
|
||||
user_id INT,
|
||||
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
order_status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
|
||||
total_amount DECIMAL(10,2),
|
||||
net_amount DECIMAL(10,2),
|
||||
shipping_address_id INT,
|
||||
billing_address_id INT,
|
||||
payment_method VARCHAR(50)
|
||||
);
|
||||
|
||||
CREATE TABLE order_items (
|
||||
order_item_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
item_id INT,
|
||||
product_id INT,
|
||||
quantity INT NOT NULL,
|
||||
unit_price DECIMAL(10,2) NOT NULL,
|
||||
discount DECIMAL(10,2) DEFAULT 0,
|
||||
subtotal DECIMAL(10,2),
|
||||
FOREIGN KEY (order_id) REFERENCES orders(order_id),
|
||||
FOREIGN KEY (item_id) REFERENCES items(item_id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
);
|
||||
|
||||
CREATE TABLE shipments (
|
||||
shipment_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
shipment_date DATE,
|
||||
estimated_delivery DATE,
|
||||
actual_delivery DATE,
|
||||
carrier VARCHAR(100),
|
||||
tracking_number VARCHAR(100),
|
||||
status ENUM('preparing', 'shipped', 'in_transit', 'delivered') DEFAULT 'preparing'
|
||||
);
|
||||
|
||||
CREATE TABLE product_suppliers (
|
||||
product_id INT NOT NULL,
|
||||
supplier_id INT NOT NULL,
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
purchase_price DECIMAL(10,2),
|
||||
PRIMARY KEY (product_id, supplier_id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
);
|
||||
|
||||
CREATE TABLE user_wishlists (
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
added_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
priority INT DEFAULT 1,
|
||||
PRIMARY KEY (user_id, product_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id)
|
||||
);
|
||||
|
||||
CREATE TABLE payments (
|
||||
payment_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
payment_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
amount DECIMAL(10,2),
|
||||
payment_type VARCHAR(50),
|
||||
transaction_id VARCHAR(100),
|
||||
payment_status ENUM('pending', 'completed', 'failed', 'refunded')
|
||||
);
|
||||
|
||||
CREATE TABLE reviews (
|
||||
review_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
rating INT CHECK (rating >= 1 AND rating <= 5),
|
||||
comment TEXT,
|
||||
review_date DATE,
|
||||
helpful_count INT DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE promotions (
|
||||
promo_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
promo_code VARCHAR(50) UNIQUE,
|
||||
promo_name VARCHAR(100),
|
||||
discount_type ENUM('percentage', 'fixed', 'buy_one_get_one'),
|
||||
discount_value DECIMAL(10,2),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
minimum_purchase DECIMAL(10,2)
|
||||
);
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
|
||||
|
||||
这是一个非常棒的想法。为了全面测试大语言模型(LLM)的 SQL 能力,我们需要一个既有业务逻辑深度,又包含大量“坑”的数据库设计。
|
||||
|
||||
我为你设计了一个 **“高校教务与捐赠管理系统”**。
|
||||
|
||||
这个场景足够复杂,包含了层级结构(学院-系)、多对多关系(学生-课程、教师-课程),以及容易混淆的财务和人员信息。
|
||||
|
||||
### 1. 数据库概览
|
||||
|
||||
这个数据库包含 6 张表,以下是它们的关系和陷阱说明:
|
||||
|
||||
* **Faculties (学院表)**: 顶级组织单位。
|
||||
* **Departments (系/专业表)**: 隶属于学院,学生和教师归属于此。
|
||||
* *关联*: `Faculties` 1:N `Departments`
|
||||
* **Teachers (教师表)**: 包含入职信息。
|
||||
* *关联*: `Departments` 1:N `Teachers`
|
||||
* **Students (学生表)**: 包含学籍信息。
|
||||
* *关联*: `Departments` 1:N `Students`
|
||||
* *关联*: `Teachers` N:M (通过 Advisor 关系,这里简化为字段,实际复杂场景可用表)
|
||||
* **Courses (课程表)**: 课程基础信息。
|
||||
* **Course_Records (选课及成绩表)**: 核心多对多表。
|
||||
* *关联*: `Students` N:M `Courses`
|
||||
* *关联*: `Teachers` 1:N (一门课一个老师教,但老师教多门课)
|
||||
|
||||
---
|
||||
|
||||
### 2. 详细表结构设计 (SQL DDL)
|
||||
|
||||
你可以直接运行以下 SQL 创建表。
|
||||
|
||||
```sql
|
||||
-- 1. 学院表
|
||||
CREATE TABLE Faculties (
|
||||
faculty_id INT PRIMARY KEY,
|
||||
faculty_name VARCHAR(100),
|
||||
dean_name VARCHAR(100) -- 陷阱1: 这里的名字是人名,不是学院名
|
||||
);
|
||||
|
||||
-- 2. 系/专业表
|
||||
CREATE TABLE Departments (
|
||||
dept_id INT PRIMARY KEY,
|
||||
dept_name VARCHAR(100),
|
||||
faculty_id INT,
|
||||
FOREIGN KEY (faculty_id) REFERENCES Faculties(faculty_id),
|
||||
dept_code VARCHAR(10) UNIQUE -- 陷阱2: 类似ID但不是主键,容易搞混
|
||||
);
|
||||
|
||||
-- 3. 教师表
|
||||
CREATE TABLE Teachers (
|
||||
teacher_id INT PRIMARY KEY,
|
||||
teacher_name VARCHAR(100),
|
||||
dept_id INT,
|
||||
hire_date DATE,
|
||||
title VARCHAR(50), -- 职称:Professor, Lecturer
|
||||
salary DECIMAL(10, 2), -- 陷阱3: 薪资字段,可能被误用为学生的学费
|
||||
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
|
||||
);
|
||||
|
||||
-- 4. 学生表
|
||||
CREATE TABLE Students (
|
||||
student_id INT PRIMARY KEY,
|
||||
student_name VARCHAR(100),
|
||||
dept_id INT,
|
||||
enrollment_year INT, -- 陷阱4: 这里存的是年份(2023),不是日期
|
||||
admission_date DATE, -- 陷阱5: 这才是真正的日期
|
||||
tuition_fee DECIMAL(10, 2), -- 陷阱6: 学费,容易和 teacher.salary 搞混
|
||||
status VARCHAR(20), -- 状态: Active, Graduated, Suspended
|
||||
advisor_id INT, -- 导师ID
|
||||
total_credit INT DEFAULT 0, -- 陷阱7: 已修总学分,容易和课程的 credit 搞混
|
||||
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id),
|
||||
FOREIGN KEY (advisor_id) REFERENCES Teachers(teacher_id)
|
||||
);
|
||||
|
||||
-- 5. 课程表
|
||||
CREATE TABLE Courses (
|
||||
course_id INT PRIMARY KEY,
|
||||
course_name VARCHAR(100),
|
||||
credit INT, -- 学分
|
||||
course_type VARCHAR(50), -- 类型: Core, Elective
|
||||
dept_id INT, -- 开课系
|
||||
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
|
||||
);
|
||||
|
||||
-- 6. 选课及成绩表 (多对多关系)
|
||||
CREATE TABLE Course_Records (
|
||||
record_id INT PRIMARY KEY,
|
||||
student_id INT,
|
||||
course_id INT,
|
||||
teacher_id INT,
|
||||
semester VARCHAR(20), -- 例如 "2023-Fall"
|
||||
score DECIMAL(5, 2), -- 陷阱8: 卷面分数 (0-100)
|
||||
grade_point DECIMAL(3, 2), -- 陷阱9: 绩点 (0.0-4.0),含义相近易混
|
||||
attendance_rate INT, -- 考勤率百分比
|
||||
FOREIGN KEY (student_id) REFERENCES Students(student_id),
|
||||
FOREIGN KEY (course_id) REFERENCES Courses(course_id),
|
||||
FOREIGN KEY (teacher_id) REFERENCES Teachers(teacher_id)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 迷惑项与陷阱设计解析
|
||||
|
||||
在设计上述数据库时,我特意埋下了以下几类“坑”,用于测试 LLM 的语义理解能力:
|
||||
|
||||
1. **相似的字段名与含义**:
|
||||
* `Students.enrollment_year` (年份, Integer) vs `Students.admission_date` (日期, Date)。
|
||||
* *测试点*: 模型能否识别“年份”和“日期”的区别,在写 WHERE 条件时不会用错。
|
||||
* `Course_Records.score` (考试分数) vs `Course_Records.grade_point` (绩点) vs `Courses.credit` (学分)。
|
||||
* *测试点*: 当问“平均分”时,模型应该算 `score`;当问“总学分”时,应该累加 `Courses.credit`。模型经常会混淆“分”和“点”。
|
||||
* `Teachers.salary` (工资) vs `Students.tuition_fee` (学费)。
|
||||
* *测试点*: 两个字段都是钱,模型在写 JOIN 时如果只关注金额字段,很容易连错表。
|
||||
|
||||
2. **层级关系**:
|
||||
* `Faculties` -> `Departments` -> `Students/Teachers`。
|
||||
* *测试点*: 如果问题是“统计 **计算机学院** (Faculty) 的所有学生人数”,模型必须通过 `Departments` 表进行两跳连接 (`Students` -> `Departments` -> `Faculties`),而不能直接连。
|
||||
|
||||
3. **聚合函数的干扰**:
|
||||
* `Students.total_credit` (表中已存在的冗余字段) vs 动态计算 `SUM(Courses.credit)`。
|
||||
* *测试点*: 严谨的 SQL 应该基于 `Course_Records` 和 `Courses` 动态计算,但如果模型偷懒,可能会直接读取 `Students.total_credit`。我们可以测试问“实际修读学分”看模型是读字段还是计算。
|
||||
|
||||
4. **多对多关系的中间表**:
|
||||
* `Course_Records` 包含了 `student_id`, `course_id`, `teacher_id`。
|
||||
* *测试点*: 问“谁教了张三?”,模型需要 JOIN `Students` -> `Course_Records` -> `Teachers`。如果模型误以为 `Students.advisor_id` 就是任课老师,就会写错。
|
||||
|
||||
5. **命名相似**:
|
||||
* `dept_id` 与 `dept_code`。
|
||||
* *测试点*: 如果在查询条件中给出了代码(如 'CS01'),模型必须知道是用 `dept_code` 而不是 `dept_id` 去匹配。
|
||||
|
||||
---
|
||||
|
||||
### 4. 插入测试数据
|
||||
|
||||
为了让测试更真实,这里插入几条基础数据:
|
||||
|
||||
```sql
|
||||
-- Faculties
|
||||
INSERT INTO Faculties VALUES (1, 'Engineering', 'Dr. Smith');
|
||||
INSERT INTO Faculties VALUES (2, 'Arts', 'Dr. Joyce');
|
||||
|
||||
-- Departments
|
||||
INSERT INTO Departments VALUES (101, 'Computer Science', 1, 'CS');
|
||||
INSERT INTO Departments VALUES (102, 'Mechanical Eng', 1, 'ME');
|
||||
INSERT INTO Departments VALUES (201, 'English Lit', 2, 'ENG');
|
||||
|
||||
-- Teachers
|
||||
INSERT INTO Teachers VALUES (1, 'Prof. Alice', 101, '2020-01-01', 'Professor', 9000.00);
|
||||
INSERT INTO Teachers VALUES (2, 'Dr. Bob', 101, '2021-06-01', 'Lecturer', 6000.00);
|
||||
INSERT INTO Teachers VALUES (3, 'Prof. Carol', 201, '2019-09-01', 'Professor', 8500.00);
|
||||
|
||||
-- Students
|
||||
-- 注意:张三和李四在同一个系,王五在不同系
|
||||
INSERT INTO Students VALUES (1001, 'Zhang San', 101, 2022, '2022-09-01', 5000.00, 'Active', 1, 0);
|
||||
INSERT INTO Students VALUES (1002, 'Li Si', 101, 2023, '2023-09-01', 5500.00, 'Active', 2, 0);
|
||||
INSERT INTO Students VALUES (1003, 'Wang Wu', 201, 2022, '2022-09-01', 4500.00, 'Active', 3, 0);
|
||||
|
||||
-- Courses
|
||||
INSERT INTO Courses VALUES (501, 'Database Systems', 4, 'Core', 101);
|
||||
INSERT INTO Courses VALUES (502, 'Algorithms', 3, 'Core', 101);
|
||||
INSERT INTO Courses VALUES (503, 'Shakespeare', 3, 'Elective', 201);
|
||||
|
||||
-- Course_Records (选课记录)
|
||||
-- 张三选了数据库,老师是Alice,考了85分
|
||||
INSERT INTO Course_Records VALUES (1, 1001, 501, 1, '2023-Fall', 85.0, 3.5, 90);
|
||||
-- 张三选了算法,老师是Bob,考了92分
|
||||
INSERT INTO Course_Records VALUES (2, 1001, 502, 2, '2023-Fall', 92.0, 4.0, 95);
|
||||
-- 李四选了数据库,老师是Alice,考了78分
|
||||
INSERT INTO Course_Records VALUES (3, 1002, 501, 1, '2023-Fall', 78.0, 2.7, 80);
|
||||
-- 王五选了莎士比亚,老师是Carol
|
||||
INSERT INTO Course_Records VALUES (4, 1003, 503, 3, '2023-Fall', 88.0, 3.7, 100);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 推荐的测试 Prompt 样例
|
||||
|
||||
你可以用下面这些问题去测试模型,观察它是否掉入陷阱:
|
||||
|
||||
1. **测试层级过滤与相似字段名**:
|
||||
* *问题*: “查询 **Engineering 学院** (Faculty) 下所有学生的名字和他们的入学年份。”
|
||||
* *陷阱*: 需要两跳 JOIN;必须选 `enrollment_year` 而不是 `admission_date`。
|
||||
|
||||
2. **测试多对多关系与字段混淆**:
|
||||
* *问题*: “查询 ‘Database Systems’ 这门课的平均考试分数。”
|
||||
* *陷阱*: 必须选 `Course_Records.score`,不能选 `grade_point`,也不能选 `Courses.credit`。
|
||||
|
||||
3. **测试金额字段区分**:
|
||||
* *问题*: “列出所有学费高于 5000 的学生姓名以及他们所属系的名称。”
|
||||
* *陷阱*: 必须用 `Students.tuition_fee`,如果模型写了 `salary` 就错了。
|
||||
|
||||
4. **测试聚合与 JOIN 顺序**:
|
||||
* *问题*: “找出教过学生 ‘Zhang San’ 的所有老师的名字。”
|
||||
* *陷阱*: 路径是 `Students` -> `Course_Records` -> `Teachers`。不能直接用 `Students.advisor_id`,因为那只是导师,不一定教过课。
|
||||
|
||||
5. **测试相似表名/代码**:
|
||||
* *问题*: “查询系代码为 'CS' 的系里,职称是 'Professor' 的老师薪资总和。”
|
||||
* *陷阱*: 条件是用 `dept_code = 'CS'` 而不是 `dept_id`。
|
||||
|
||||
这套设计应该能很好地帮你分辨出 LLM 在 Schema Linking(模式链接)和语义理解方面的真实水平。
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
CREATE TABLE Faculties (
|
||||
faculty_id INT PRIMARY KEY,
|
||||
faculty_name VARCHAR(100),
|
||||
dean_name VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE Departments (
|
||||
dept_id INT PRIMARY KEY,
|
||||
dept_name VARCHAR(100),
|
||||
faculty_id INT,
|
||||
FOREIGN KEY (faculty_id) REFERENCES Faculties(faculty_id),
|
||||
dept_code VARCHAR(10) UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE Teachers (
|
||||
teacher_id INT PRIMARY KEY,
|
||||
teacher_name VARCHAR(100),
|
||||
dept_id INT,
|
||||
hire_date DATE,
|
||||
title VARCHAR(50),
|
||||
salary DECIMAL(10, 2),
|
||||
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
|
||||
);
|
||||
|
||||
CREATE TABLE Students (
|
||||
student_id INT PRIMARY KEY,
|
||||
student_name VARCHAR(100),
|
||||
dept_id INT,
|
||||
enrollment_year INT,
|
||||
admission_date DATE,
|
||||
tuition_fee DECIMAL(10, 2),
|
||||
status VARCHAR(20),
|
||||
advisor_id INT,
|
||||
total_credit INT DEFAULT 0,
|
||||
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id),
|
||||
FOREIGN KEY (advisor_id) REFERENCES Teachers(teacher_id)
|
||||
);
|
||||
|
||||
CREATE TABLE Courses (
|
||||
course_id INT PRIMARY KEY,
|
||||
course_name VARCHAR(100),
|
||||
credit INT,
|
||||
course_type VARCHAR(50),
|
||||
dept_id INT,
|
||||
FOREIGN KEY (dept_id) REFERENCES Departments(dept_id)
|
||||
);
|
||||
|
||||
CREATE TABLE Course_Records (
|
||||
record_id INT PRIMARY KEY,
|
||||
student_id INT,
|
||||
course_id INT,
|
||||
teacher_id INT,
|
||||
semester VARCHAR(20),
|
||||
score DECIMAL(5, 2),
|
||||
grade_point DECIMAL(3, 2),
|
||||
attendance_rate INT,
|
||||
FOREIGN KEY (student_id) REFERENCES Students(student_id),
|
||||
FOREIGN KEY (course_id) REFERENCES Courses(course_id),
|
||||
FOREIGN KEY (teacher_id) REFERENCES Teachers(teacher_id)
|
||||
);
|
||||
|
||||
INSERT INTO Faculties VALUES (1, 'Engineering', 'Dr. Smith');
|
||||
INSERT INTO Faculties VALUES (2, 'Arts', 'Dr. Joyce');
|
||||
|
||||
INSERT INTO Departments VALUES (101, 'Computer Science', 1, 'CS');
|
||||
INSERT INTO Departments VALUES (102, 'Mechanical Eng', 1, 'ME');
|
||||
INSERT INTO Departments VALUES (201, 'English Lit', 2, 'ENG');
|
||||
|
||||
INSERT INTO Teachers VALUES (1, 'Prof. Alice', 101, '2020-01-01', 'Professor', 9000.00);
|
||||
INSERT INTO Teachers VALUES (2, 'Dr. Bob', 101, '2021-06-01', 'Lecturer', 6000.00);
|
||||
INSERT INTO Teachers VALUES (3, 'Prof. Carol', 201, '2019-09-01', 'Professor', 8500.00);
|
||||
|
||||
INSERT INTO Students VALUES (1001, 'Zhang San', 101, 2022, '2022-09-01', 5000.00, 'Active', 1, 0);
|
||||
INSERT INTO Students VALUES (1002, 'Li Si', 101, 2023, '2023-09-01', 5500.00, 'Active', 2, 0);
|
||||
INSERT INTO Students VALUES (1003, 'Wang Wu', 201, 2022, '2022-09-01', 4500.00, 'Active', 3, 0);
|
||||
|
||||
INSERT INTO Courses VALUES (501, 'Database Systems', 4, 'Core', 101);
|
||||
INSERT INTO Courses VALUES (502, 'Algorithms', 3, 'Core', 101);
|
||||
INSERT INTO Courses VALUES (503, 'Shakespeare', 3, 'Elective', 201);
|
||||
|
||||
INSERT INTO Course_Records VALUES (1, 1001, 501, 1, '2023-Fall', 85.0, 3.5, 90);
|
||||
INSERT INTO Course_Records VALUES (2, 1001, 502, 2, '2023-Fall', 92.0, 4.0, 95);
|
||||
INSERT INTO Course_Records VALUES (3, 1002, 501, 1, '2023-Fall', 78.0, 2.7, 80);
|
||||
INSERT INTO Course_Records VALUES (4, 1003, 503, 3, '2023-Fall', 88.0, 3.7, 100);
|
||||
|
|
@ -0,0 +1,682 @@
|
|||
# TestECommerceDB 数据库完整建表SQL
|
||||
|
||||
以下是完整的MySQL数据库建表脚本,包含表结构、约束、索引和测试数据。
|
||||
|
||||
```sql
|
||||
-- =====================================================
|
||||
-- 数据库创建脚本
|
||||
-- 数据库名称:TestECommerceDB
|
||||
-- 用途:测试大语言模型的SQL编写能力
|
||||
-- 设计特点:复杂关联关系、一对多、多对多、自关联
|
||||
-- 迷惑项:相似字段名、相近表名、含义相近的字段
|
||||
-- =====================================================
|
||||
|
||||
-- 创建数据库
|
||||
DROP DATABASE IF EXISTS TestECommerceDB;
|
||||
CREATE DATABASE TestECommerceDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE TestECommerceDB;
|
||||
|
||||
-- =====================================================
|
||||
-- 1. 用户表 (Users)
|
||||
-- 存储用户基本信息
|
||||
-- 迷惑项:user_id、user_email、user_status 字段名相似
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Users (
|
||||
user_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
user_email VARCHAR(100) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(100),
|
||||
phone VARCHAR(20),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
user_status TINYINT DEFAULT 1 COMMENT '1=活跃, 0=禁用',
|
||||
last_login DATETIME,
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_user_email (user_email),
|
||||
INDEX idx_user_status (user_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 2. 分类表 (Categories)
|
||||
-- 存储产品分类信息
|
||||
-- 迷惑项:自关联关系,category_id、category_name、parent_id
|
||||
-- 特点:树形结构,支持多级分类
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Categories (
|
||||
category_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
category_name VARCHAR(50) NOT NULL,
|
||||
category_desc VARCHAR(200),
|
||||
parent_id INT DEFAULT NULL,
|
||||
category_order INT DEFAULT 0,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (parent_id) REFERENCES Categories(category_id) ON DELETE SET NULL,
|
||||
INDEX idx_parent_id (parent_id),
|
||||
INDEX idx_category_name (category_name),
|
||||
INDEX idx_is_active (is_active)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 3. 产品表 (Products)
|
||||
-- 存储产品信息
|
||||
-- 迷惑项:product_id、product_name、product_desc、product_status
|
||||
-- 含义相近字段:price vs cost_price
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Products (
|
||||
product_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
product_name VARCHAR(100) NOT NULL,
|
||||
product_desc TEXT,
|
||||
product_specs VARCHAR(255) COMMENT '产品规格JSON字符串',
|
||||
price DECIMAL(10,2) NOT NULL COMMENT '销售价格',
|
||||
cost_price DECIMAL(10,2) COMMENT '成本价格',
|
||||
stock_quantity INT DEFAULT 0,
|
||||
stock_alert_level INT DEFAULT 10,
|
||||
category_id INT,
|
||||
brand VARCHAR(50),
|
||||
product_status TINYINT DEFAULT 1 COMMENT '1=上架, 0=下架, 2=缺货',
|
||||
view_count INT DEFAULT 0,
|
||||
sales_count INT DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (category_id) REFERENCES Categories(category_id) ON DELETE SET NULL,
|
||||
INDEX idx_product_name (product_name),
|
||||
INDEX idx_price (price),
|
||||
INDEX idx_category_id (category_id),
|
||||
INDEX idx_product_status (product_status),
|
||||
INDEX idx_brand (brand),
|
||||
INDEX idx_sales_count (sales_count)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 4. 产品-分类关联表 (ProductCategories)
|
||||
-- 实现产品和分类的多对多关系
|
||||
-- 迷惑项:表名与Products、Categories相似
|
||||
-- 特点:支持产品属于多个分类,设置主分类
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE ProductCategories (
|
||||
product_id INT NOT NULL,
|
||||
category_id INT NOT NULL,
|
||||
is_primary TINYINT DEFAULT 0 COMMENT '1=主分类, 0=副分类',
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (product_id, category_id),
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (category_id) REFERENCES Categories(category_id) ON DELETE CASCADE,
|
||||
INDEX idx_category_id (category_id),
|
||||
INDEX idx_is_primary (is_primary)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 5. 订单表 (Orders)
|
||||
-- 存储订单信息
|
||||
-- 迷惑项:order_id、order_date、order_status
|
||||
-- 含义相近字段:total_amount
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Orders (
|
||||
order_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_number VARCHAR(30) NOT NULL UNIQUE COMMENT '订单编号',
|
||||
user_id INT NOT NULL,
|
||||
order_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
|
||||
discount_amount DECIMAL(10,2) DEFAULT 0 COMMENT '折扣金额',
|
||||
shipping_fee DECIMAL(10,2) DEFAULT 0 COMMENT '运费',
|
||||
payment_method VARCHAR(30) COMMENT '支付方式',
|
||||
payment_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/paid/failed/refunded',
|
||||
order_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/confirmshipped/delivered/cancelled/completed',
|
||||
shipping_addr_id INT,
|
||||
billing_addr_id INT,
|
||||
shipping_addr VARCHAR(500) COMMENT '收货地址快照',
|
||||
billing_addr VARCHAR(500) COMMENT '账单地址快照',
|
||||
customer_remark VARCHAR(500),
|
||||
internal_remark VARCHAR(500),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE RESTRICT,
|
||||
INDEX idx_order_number (order_number),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_order_date (order_date),
|
||||
INDEX idx_order_status (order_status),
|
||||
INDEX idx_payment_status (payment_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 6. 订单详情表 (OrderItems)
|
||||
-- 存储订单中每个产品的详细信息
|
||||
-- 迷惑项:order_item_id、order_id
|
||||
-- 含义相近字段:item_price vs price,item_total vs total_amount
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE OrderItems (
|
||||
order_item_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL,
|
||||
item_price DECIMAL(10,2) NOT NULL COMMENT '下单时的产品单价',
|
||||
item_cost DECIMAL(10,2) COMMENT '成本单价',
|
||||
item_discount DECIMAL(10,2) DEFAULT 0 COMMENT '该项折扣',
|
||||
item_total DECIMAL(10,2) NOT NULL COMMENT '该项总金额',
|
||||
item_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/shipped/delivered/returned',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE RESTRICT,
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_item_status (item_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 7. 地址表 (Addresses)
|
||||
-- 存储用户的多个地址
|
||||
-- 迷惑项:address_id、address_line1、address_line2
|
||||
-- 一对多关系:一个用户有多个地址
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Addresses (
|
||||
address_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
address_label VARCHAR(50) COMMENT '地址标签,如:家、公司',
|
||||
recipient_name VARCHAR(100) NOT NULL,
|
||||
recipient_phone VARCHAR(20) NOT NULL,
|
||||
country VARCHAR(50) DEFAULT '中国',
|
||||
province VARCHAR(50),
|
||||
city VARCHAR(50) NOT NULL,
|
||||
district VARCHAR(50),
|
||||
address_line1 VARCHAR(200) NOT NULL,
|
||||
address_line2 VARCHAR(200),
|
||||
postal_code VARCHAR(20),
|
||||
address_type VARCHAR(20) DEFAULT 'shipping' COMMENT 'shipping/billing/both',
|
||||
is_default TINYINT DEFAULT 0,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_address_type (address_type),
|
||||
INDEX idx_is_default (is_default),
|
||||
INDEX idx_city (city)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 8. 评论表 (Reviews)
|
||||
-- 存储用户对产品的评论
|
||||
-- 迷惑项:review_id、review_text、review_date
|
||||
-- 多对一关系:评论关联用户和产品
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Reviews (
|
||||
review_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
order_id INT COMMENT '订单ID,可选',
|
||||
rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||
review_title VARCHAR(100),
|
||||
review_text TEXT,
|
||||
is_anonymous TINYINT DEFAULT 0,
|
||||
is_verified TINYINT DEFAULT 0 COMMENT '是否购买后评论',
|
||||
helpful_count INT DEFAULT 0,
|
||||
reply_count INT DEFAULT 0,
|
||||
review_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/approved/rejected',
|
||||
review_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE SET NULL,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_rating (rating),
|
||||
INDEX idx_review_status (review_status),
|
||||
INDEX idx_review_date (review_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 9. 购物车表 (CartItems)
|
||||
-- 存储用户当前购物车中的商品
|
||||
-- 迷惑项:cart_id、product_id、quantity
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE CartItems (
|
||||
cart_item_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL DEFAULT 1,
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_user_product (user_id, product_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 10. 产品浏览历史表 (ProductViews)
|
||||
-- 存储用户的浏览历史
|
||||
-- 迷惑项:view_id、product_id、view_date
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE ProductViews (
|
||||
view_id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT,
|
||||
product_id INT NOT NULL,
|
||||
view_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
session_id VARCHAR(100),
|
||||
view_duration INT DEFAULT 0 COMMENT '浏览时长(秒)',
|
||||
source_type VARCHAR(30) COMMENT '来源:search/category/recommend/direct',
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_view_date (view_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 11. 优惠券表 (Coupons)
|
||||
-- 存储优惠券信息
|
||||
-- 迷惑项:coupon_id、coupon_code、coupon_type
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Coupons (
|
||||
coupon_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
coupon_code VARCHAR(30) NOT NULL UNIQUE,
|
||||
coupon_name VARCHAR(100) NOT NULL,
|
||||
coupon_desc VARCHAR(255),
|
||||
coupon_type VARCHAR(20) DEFAULT 'percentage' COMMENT 'percentage/fixed',
|
||||
discount_value DECIMAL(10,2) NOT NULL COMMENT '折扣值(百分比或固定金额)',
|
||||
min_order_amount DECIMAL(10,2) DEFAULT 0,
|
||||
max_discount_amount DECIMAL(10,2) COMMENT '最大折扣金额',
|
||||
usage_limit INT COMMENT '总使用次数限制',
|
||||
usage_limit_per_user INT DEFAULT 1,
|
||||
usage_count INT DEFAULT 0,
|
||||
valid_from DATETIME NOT NULL,
|
||||
valid_until DATETIME NOT NULL,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
applicable_product_ids TEXT COMMENT '适用的产品ID列表,JSON格式',
|
||||
applicable_category_ids TEXT COMMENT '适用的分类ID列表,JSON格式',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_coupon_code (coupon_code),
|
||||
INDEX idx_valid_dates (valid_from, valid_until),
|
||||
INDEX idx_is_active (is_active)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 12. 用户优惠券关联表 (UserCoupons)
|
||||
-- 存储用户领取的优惠券
|
||||
-- 迷惑项:user_coupon_id、user_id、coupon_id
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE UserCoupons (
|
||||
user_coupon_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
coupon_id INT NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'available' COMMENT 'available/used/expired',
|
||||
received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
used_at DATETIME,
|
||||
order_id INT,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (coupon_id) REFERENCES Coupons(coupon_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE SET NULL,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_coupon_id (coupon_id),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 13. 产品收藏表 (Wishlists)
|
||||
-- 存储用户收藏的产品
|
||||
-- 迷惑项:wishlist_id、product_id、user_id
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Wishlists (
|
||||
wishlist_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
note VARCHAR(255),
|
||||
priority INT DEFAULT 0 COMMENT '优先级排序',
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_user_product (user_id, product_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_added_at (added_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 14. 支付记录表 (Payments)
|
||||
-- 存储订单支付信息
|
||||
-- 迷惑项:payment_id、order_id、payment_amount
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Payments (
|
||||
payment_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_id INT NOT NULL,
|
||||
payment_method VARCHAR(30) NOT NULL,
|
||||
payment_amount DECIMAL(10,2) NOT NULL,
|
||||
transaction_id VARCHAR(100) UNIQUE,
|
||||
payment_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/success/failed/refunded',
|
||||
payment_date DATETIME,
|
||||
refund_amount DECIMAL(10,2) DEFAULT 0,
|
||||
refund_reason VARCHAR(255),
|
||||
gateway_response TEXT COMMENT '支付网关响应',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE RESTRICT,
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_transaction_id (transaction_id),
|
||||
INDEX idx_payment_status (payment_status),
|
||||
INDEX idx_payment_date (payment_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 15. 管理员表 (Admins)
|
||||
-- 存储管理员信息
|
||||
-- 迷惑项:admin_id、admin_email、admin_role
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Admins (
|
||||
admin_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
admin_email VARCHAR(100) NOT NULL UNIQUE,
|
||||
admin_name VARCHAR(50) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
admin_role VARCHAR(30) DEFAULT 'staff' COMMENT 'super_admin/admin/staff',
|
||||
is_active TINYINT DEFAULT 1,
|
||||
last_login DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_admin_email (admin_email),
|
||||
INDEX idx_admin_role (admin_role)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 添加外键约束到Orders表的shipping_addr_id和billing_addr_id
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE Orders
|
||||
ADD CONSTRAINT fk_orders_shipping_addr
|
||||
FOREIGN KEY (shipping_addr_id) REFERENCES Addresses(address_id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE Orders
|
||||
ADD CONSTRAINT fk_orders_billing_addr
|
||||
FOREIGN KEY (billing_addr_id) REFERENCES Addresses(address_id) ON DELETE SET NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- 插入测试数据
|
||||
-- =====================================================
|
||||
|
||||
-- 插入分类数据(树形结构)
|
||||
INSERT INTO Categories (category_id, category_name, category_desc, parent_id, category_order) VALUES
|
||||
(1, '电子产品', '手机、电脑、相机等电子设备', NULL, 1),
|
||||
(2, '服装', '男装、女装、童装等', NULL, 2),
|
||||
(3, '家居用品', '家具、厨具、床上用品等', NULL, 3),
|
||||
(4, '食品', '零食、饮料、生鲜等', NULL, 4),
|
||||
(5, '手机', '智能手机', 1, 1),
|
||||
(6, '笔记本电脑', '笔记本、台式机', 1, 2),
|
||||
(7, '相机', '数码相机、摄像机', 1, 3),
|
||||
(8, '男装', '男性服装', 2, 1),
|
||||
(9, '女装', '女性服装', 2, 2),
|
||||
(10, '智能手机', '高端智能手机', 5, 1),
|
||||
(11, '功能手机', '老年机、备用机', 5, 2);
|
||||
|
||||
-- 插入用户数据
|
||||
INSERT INTO Users (user_id, username, user_email, password_hash, full_name, phone, user_status) VALUES
|
||||
(1, 'zhangsan', 'zhangsan@example.com', 'hash_abc123', '张三', '13800138001', 1),
|
||||
(2, 'lisi', 'lisi@example.com', 'hash_def456', '李四', '13800138002', 1),
|
||||
(3, 'wangwu', 'wangwu@example.com', 'hash_ghi789', '王五', '13800138003', 1),
|
||||
(4, 'zhaoliu', 'zhaoliu@example.com', 'hash_jkl012', '赵六', '13800138004', 0),
|
||||
(5, 'qianqi', 'qianqi@example.com', 'hash_mno345', '钱七', '13800138005', 1),
|
||||
(6, 'sunba', 'sunba@example.com', 'hash_pqr678', '孙八', '13800138006', 1),
|
||||
(7, 'zhoujiu', 'zhoujiu@example.com', 'hash_stu901', '周九', '13800138007', 1),
|
||||
(8, 'wushiyi', 'wushiyi@example.com', 'hash_vwx234', '吴十', '13800138008', 1),
|
||||
(9, 'zhengshier', 'zhengshier@example.com', 'hash_yza567', '郑十二', '13800138009', 1),
|
||||
(10, 'xushisan', 'xushisan@example.com', 'hash_bcd890', '徐十三', '13800138010', 1);
|
||||
|
||||
-- 插入产品数据
|
||||
INSERT INTO Products (product_id, product_name, product_desc, price, cost_price, stock_quantity, category_id, brand, product_status) VALUES
|
||||
(1, 'iPhone 15 Pro Max', '苹果最新旗舰手机,A17 Pro芯片,钛金属机身', 9999.00, 6500.00, 100, 10, 'Apple', 1),
|
||||
(2, 'Samsung Galaxy S24 Ultra', '三星Galaxy S24 Ultra,钛金属机身,AI功能', 8999.00, 5800.00, 80, 10, 'Samsung', 1),
|
||||
(3, 'MacBook Pro 16', '苹果MacBook Pro 16英寸,M3 Max芯片', 24999.00, 18000.00, 50, 6, 'Apple', 1),
|
||||
(4, 'Dell XPS 15', '戴尔XPS 15笔记本电脑,英特尔酷睿i7', 12999.00, 9500.00, 60, 6, 'Dell', 1),
|
||||
(5, 'Sony A7 IV', '索尼A7 IV全画幅微单相机,3300万像素', 16999.00, 12000.00, 30, 7, 'Sony', 1),
|
||||
(6, 'Canon R6 Mark II', '佳能R6 Mark II全画幅微单相机', 15999.00, 11500.00, 25, 7, 'Canon', 1),
|
||||
(7, 'Nike Air Jordan 1', '耐克Air Jordan 1经典篮球鞋', 1299.00, 650.00, 200, 8, 'Nike', 1),
|
||||
(8, 'Adidas Ultraboost', '阿迪达斯Ultraboost跑鞋', 1099.00, 550.00, 150, 8, 'Adidas', 1),
|
||||
(9, 'Zara女士连衣裙', 'Zara时尚女士连衣裙,2024新款', 599.00, 200.00, 300, 9, 'Zara', 1),
|
||||
(10, 'H&M半身裙', 'H&M时尚半身裙,百搭款', 299.00, 100.00, 400, 9, 'H&M', 1),
|
||||
(11, 'IKEA双人床', '宜家马尔姆双人床,简约风格', 2999.00, 1800.00, 20, 3, 'IKEA', 1),
|
||||
(12, '美的微波炉', '美的M1-L213B微波炉,21L容量', 499.00, 280.00, 100, 3, '美的', 1),
|
||||
(13, '三只松鼠坚果大礼包', '三只松鼠坚果礼盒2103g', 128.00, 55.00, 500, 4, '三只松鼠', 1),
|
||||
(14, '可口可乐330ml*24罐', '可口可乐经典装24罐装', 59.90, 35.00, 1000, 4, '可口可乐', 1),
|
||||
(15, '老干妈辣椒酱', '老干妈风味豆豉油辣椒酱280g', 12.90, 5.00, 2000, 4, '老干妈', 1);
|
||||
|
||||
-- 插入产品-分类关联数据
|
||||
INSERT INTO ProductCategories (product_id, category_id, is_primary) VALUES
|
||||
(1, 5, 1), (1, 10, 1),
|
||||
(2, 5, 1), (2, 10, 1),
|
||||
(3, 6, 1),
|
||||
(4, 6, 1),
|
||||
(5, 7, 1),
|
||||
(6, 7, 1),
|
||||
(7, 8, 1),
|
||||
(8, 8, 1),
|
||||
(9, 9, 1),
|
||||
(10, 9, 1),
|
||||
(11, 3, 1),
|
||||
(12, 3, 1),
|
||||
(13, 4, 1),
|
||||
(14, 4, 1),
|
||||
(15, 4, 1);
|
||||
|
||||
-- 插入地址数据
|
||||
INSERT INTO Addresses (address_id, user_id, address_label, recipient_name, recipient_phone, province, city, district, address_line1, postal_code, address_type, is_default) VALUES
|
||||
(1, 1, '家', '张三', '13800138001', '北京', '北京市', '朝阳区', '建国路100号', '100000', 'shipping', 1),
|
||||
(2, 1, '公司', '张三', '13800138001', '北京', '北京市', '海淀区', '中关村大街1号', '100080', 'shipping', 0),
|
||||
(3, 2, '默认地址', '李四', '13800138002', '上海', '上海市', '浦东新区', '陆家嘴环路1000号', '200120', 'both', 1),
|
||||
(4, 3, '家', '王五', '13800138003', '广东', '深圳市', '南山区', '科技园路100号', '518000', 'shipping', 1),
|
||||
(5, 4, '地址1', '赵六', '13800138004', '浙江', '杭州市', '西湖区', '文三路100号', '310000', 'shipping', 1),
|
||||
(6, 5, '家', '钱七', '13800138005', '江苏', '南京市', '鼓楼区', '中山北路100号', '210000', 'shipping', 1);
|
||||
|
||||
-- 插入订单数据
|
||||
INSERT INTO Orders (order_id, order_number, user_id, total_amount, discount_amount, shipping_fee, payment_status, order_status, shipping_addr_id, billing_addr_id, shipping_addr, customer_remark) VALUES
|
||||
(1, 'ORD202401010001', 1, 10998.00, 0.00, 0.00, 'paid', 'delivered', 1, 1, '北京市朝阳区建国路100号', '请尽快发货'),
|
||||
(2, 'ORD202401020001', 1, 25998.00, 500.00, 0.00, 'paid', 'shipped', 2, 2, '北京市海淀区中关村大街1号', '工作日收货'),
|
||||
(3, 'ORD202401030001', 2, 17998.00, 0.00, 15.00, 'paid', 'pending', 3, 3, '上海市浦东新区陆家嘴环路1000号', ''),
|
||||
(4, 'ORD202401040001', 3, 32998.00, 1000.00, 0.00, 'paid', 'completed', 4, 4, '广东省深圳市南山区科技园路100号', '包装仔细一些'),
|
||||
(5, 'ORD202401050001', 5, 258.90, 20.00, 0.00, 'paid', 'pending', 6, 6, '江苏省南京市鼓楼区中山北路100号', ''),
|
||||
(6, 'ORD202401060001', 2, 1398.00, 0.00, 10.00, 'pending', 'pending', 3, 3, '上海市浦东新区陆家嘴环路1000号', ''),
|
||||
(7, 'ORD202401070001', 7, 8999.00, 899.00, 0.00, 'paid', 'shipped', NULL, NULL, '四川省成都市武侯区天府大道100号', '生日礼物'),
|
||||
(8, 'ORD202401080001', 1, 541.80, 0.00, 8.00, 'paid', 'delivered', 1, 1, '北京市朝阳区建国路100号', ''),
|
||||
(9, 'ORD202401090001', 8, 16999.00, 0.00, 0.00, 'paid', 'delivered', NULL, NULL, '湖北省武汉市洪山区珞珈山路100号', ''),
|
||||
(10, 'ORD202401100001', 9, 128.00, 10.00, 0.00, 'paid', 'pending', NULL, NULL, '湖南省长沙市岳麓区麓山南路100号', '');
|
||||
|
||||
-- 插入订单详情数据
|
||||
INSERT INTO OrderItems (order_item_id, order_id, product_id, quantity, item_price, item_cost, item_total, item_status) VALUES
|
||||
(1, 1, 1, 1, 9999.00, 6500.00, 9999.00, 'delivered'),
|
||||
(2, 1, 14, 24, 59.90, 35.00, 1437.60, 'delivered'),
|
||||
(3, 2, 3, 1, 24999.00, 18000.00, 24999.00, 'shipped'),
|
||||
(4, 2, 15, 7, 12.90, 5.00, 90.30, 'shipped'),
|
||||
(5, 3, 2, 2, 8999.00, 5800.00, 17998.00, 'pending'),
|
||||
(6, 4, 5, 1, 16999.00, 12000.00, 16999.00, 'delivered'),
|
||||
(7, 4, 6, 1, 15999.00, 11500.00, 15999.00, 'delivered'),
|
||||
(8, 5, 9, 1, 599.00, 200.00, 599.00, 'pending'),
|
||||
(9, 5, 10, 2, 299.00, 100.00, 598.00, 'pending'),
|
||||
(10, 6, 7, 1, 1299.00, 650.00, 1299.00, 'pending'),
|
||||
(11, 7, 2, 1, 8999.00, 5800.00, 8999.00, 'shipped'),
|
||||
(12, 8, 11, 1, 2999.00, 1800.00, 2999.00, 'delivered'),
|
||||
(13, 8, 12, 1, 499.00, 280.00, 499.00, 'delivered'),
|
||||
(14, 8, 13, 1, 128.00, 55.00, 128.00, 'delivered'),
|
||||
(15, 9, 6, 1, 15999.00, 11500.00, 15999.00, 'delivered'),
|
||||
(16, 10, 13, 1, 128.00, 55.00, 128.00, 'pending');
|
||||
|
||||
-- 插入评论数据
|
||||
INSERT INTO Reviews (review_id, user_id, product_id, order_id, rating, review_title, review_text, is_verified, review_status) VALUES
|
||||
(1, 1, 1, 1, 5, '非常满意', '手机质感很好,拍照效果出色,电池续航也比之前的手机好很多。', 1, 'approved'),
|
||||
(2, 2, 2, 3, 4, '不错', '系统流畅,屏幕显示效果好,但是价格有点贵。', 1, 'approved'),
|
||||
(3, 3, 5, 4, 5, '专业级相机', '对焦速度快,高感光度表现优秀,非常适合专业摄影。', 1, 'approved'),
|
||||
(4, 1, 3, 2, 5, '性能怪兽', 'M3 Max芯片性能太强了,编译代码速度飞快。', 1, 'approved'),
|
||||
(5, 4, 7, NULL, 3, '一般', '鞋子样式不错,但是鞋底偏硬,走路久了脚疼。', 0, 'approved'),
|
||||
(6, 5, 9, 5, 4, '好看', '穿上身效果很好,面料也很舒服,推荐购买。', 1, 'approved'),
|
||||
(7, 6, 11, NULL, 5, '安装方便', '床很结实,安装也很简单,一个人就能搞定。', 0, 'approved'),
|
||||
(8, 7, 14, 7, 5, '全家喜欢', '家里常备的可乐,味道正宗,价格实惠。', 1, 'approved'),
|
||||
(9, 8, 6, 9, 4, '功能强大', '相机很好用,但是菜单操作有点复杂,需要时间学习。', 1, 'approved'),
|
||||
(10, 9, 13, 10, 5, '坚果新鲜', '坚果很新鲜,种类多,口感好,已经复购好几次了。', 1, 'approved'),
|
||||
(11, 2, 15, NULL, 4, '下饭神器', '辣椒酱味道很好,用来拌饭很香。', 0, 'approved'),
|
||||
(12, 1, 14, 1, 5, '实惠', '比超市便宜很多,一箱能喝很久。', 1, 'approved');
|
||||
|
||||
-- 插入购物车数据
|
||||
INSERT INTO CartItems (user_id, product_id, quantity, added_at) VALUES
|
||||
(1, 2, 1, '2024-01-10 10:00:00'),
|
||||
(1, 5, 1, '2024-01-10 10:05:00'),
|
||||
(2, 3, 1, '2024-01-10 11:00:00'),
|
||||
(2, 7, 2, '2024-01-10 11:10:00'),
|
||||
(3, 1, 1, '2024-01-10 12:00:00'),
|
||||
(4, 8, 1, '2024-01-10 13:00:00'),
|
||||
(5, 9, 2, '2024-01-10 14:00:00'),
|
||||
(6, 10, 1, '2024-01-10 15:00:00'),
|
||||
(7, 11, 1, '2024-01-10 16:00:00'),
|
||||
(8, 12, 2, '2024-01-10 17:00:00');
|
||||
|
||||
-- 插入优惠券数据
|
||||
INSERT INTO Coupons (coupon_id, coupon_code, coupon_name, coupon_desc, coupon_type, discount_value, min_order_amount, max_discount_amount, usage_limit, valid_from, valid_until, is_active) VALUES
|
||||
(1, 'NEWUSER10', '新用户专享10%off', '新用户首单立享10%折扣', 'percentage', 10.00, 100.00, 500.00, 10000, '2024-01-01', '2024-12-31', 1),
|
||||
(2, 'SAVE50', '满500减50', '订单满500元减50元', 'fixed', 50.00, 500.00, NULL, NULL, '2024-01-01', '2024-12-31', 1),
|
||||
(3, 'VIP20', 'VIP会员20%off', 'VIP会员享20%折扣', 'percentage', 20.00, 200.00, 1000.00, 5000, '2024-01-01', '2024-12-31', 1),
|
||||
(4, 'FREESHIP', '免运费券', '订单免运费', 'fixed', 0.00, 0.00, NULL, NULL, '2024-01-01', '2024-12-31', 1),
|
||||
(5, 'SUPER100', '超级满减100', '满1000减100', 'fixed', 100.00, 1000.00, NULL, 1000, '2024-01-01', '2024-06-30', 1),
|
||||
(6, 'EXPIRED50', '已过期优惠券', '测试用过期优惠券', 'fixed', 50.00, 200.00, NULL, NULL, '2023-01-01', '2023-12-31', 1);
|
||||
|
||||
-- 插入用户优惠券数据
|
||||
INSERT INTO UserCoupons (user_id, coupon_id, status, used_at, order_id) VALUES
|
||||
(1, 1, 'used', '2024-01-01 10:00:00', 1),
|
||||
(1, 2, 'available', NULL, NULL),
|
||||
(1, 3, 'available', NULL, NULL),
|
||||
(2, 1, 'used', '2024-01-03 10:00:00', 3),
|
||||
(2, 4, 'available', NULL, NULL),
|
||||
(3, 2, 'available', NULL, NULL),
|
||||
(4, 1, 'available', NULL, NULL),
|
||||
(5, 5, 'used', '2024-01-05 10:00:00', 5),
|
||||
(6, 3, 'available', NULL, NULL),
|
||||
(7, 4, 'used', '2024-01-07 10:00:00', 7);
|
||||
|
||||
-- 插入收藏数据
|
||||
INSERT INTO Wishlists (user_id, product_id, added_at, note, priority) VALUES
|
||||
(1, 6, '2024-01-05 10:00:00', '等降价再买', 1),
|
||||
(1, 8, '2024-01-06 10:00:00', NULL, 2),
|
||||
(2, 3, '2024-01-05 11:00:00', '想要', 1),
|
||||
(2, 4, '2024-01-06 11:00:00', '备选', 2),
|
||||
(3, 1, '2024-01-05 12:00:00', '送给女朋友', 1),
|
||||
(4, 9, '2024-01-05 13:00:00', NULL, 1),
|
||||
(5, 11, '2024-01-05 14:00:00', '搬家时买', 1),
|
||||
(6, 13, '2024-01-05 15:00:00', NULL, 1);
|
||||
|
||||
-- 插入支付记录数据
|
||||
INSERT INTO Payments (order_id, payment_method, payment_amount, transaction_id, payment_status, payment_date) VALUES
|
||||
(1, 'alipay', 10998.00, 'TXN202401010001', 'success', '2024-01-01 10:05:00'),
|
||||
(2, 'wechat', 25998.00, 'TXN202401020001', 'success', '2024-01-02 10:05:00'),
|
||||
(3, 'alipay', 17998.00, 'TXN202401030001', 'success', '2024-01-03 10:05:00'),
|
||||
(4, 'credit_card', 32998.00, 'TXN202401040001', 'success', '2024-01-04 10:05:00'),
|
||||
(5, 'wechat', 258.90, 'TXN202401050001', 'success', '2024-01-05 10:05:00'),
|
||||
(7, 'alipay', 8999.00, 'TXN202401070001', 'success', '2024-01-07 10:05:00'),
|
||||
(8, 'wechat', 541.80, 'TXN202401080001', 'success', '2024-01-08 10:05:00'),
|
||||
(9, 'credit_card', 16999.00, 'TXN202401090001', 'success', '2024-01-09 10:05:00'),
|
||||
(10, 'alipay', 128.00, 'TXN202401100001', 'success', '2024-01-10 10:05:00');
|
||||
|
||||
-- 插入管理员数据
|
||||
INSERT INTO Admins (admin_id, admin_email, admin_name, password_hash, admin_role) VALUES
|
||||
(1, 'admin@example.com', '超级管理员', 'hash_superadmin', 'super_admin'),
|
||||
(2, 'manager@example.com', '运营经理', 'hash_manager', 'admin'),
|
||||
(3, 'staff@example.com', '客服人员', 'hash_staff', 'staff'),
|
||||
(4, 'finance@example.com', '财务人员', 'hash_finance', 'admin');
|
||||
|
||||
-- 插入产品浏览历史数据
|
||||
INSERT INTO ProductViews (user_id, product_id, view_date, session_id, view_duration, source_type) VALUES
|
||||
(1, 1, '2024-01-01 09:00:00', 'SES001', 120, 'search'),
|
||||
(1, 2, '2024-01-01 09:05:00', 'SES001', 90, 'category'),
|
||||
(1, 3, '2024-01-02 10:00:00', 'SES002', 180, 'recommend'),
|
||||
(2, 1, '2024-01-03 11:00:00', 'SES003', 60, 'search'),
|
||||
(2, 5, '2024-01-03 11:10:00', 'SES003', 240, 'direct'),
|
||||
(3, 6, '2024-01-04 12:00:00', 'SES004', 300, 'search'),
|
||||
(4, 7, '2024-01-05 13:00:00', 'SES005', 45, 'category'),
|
||||
(5, 9, '2024-01-06 14:00:00', 'SES006', 120, 'search'),
|
||||
(6, 11, '2024-01-07 15:00:00', 'SES007', 90, 'recommend'),
|
||||
(7, 13, '2024-01-08 16:00:00', 'SES008', 30, 'direct');
|
||||
|
||||
-- =====================================================
|
||||
-- 验证数据完整性
|
||||
-- =====================================================
|
||||
|
||||
-- 查看所有表
|
||||
SHOW TABLES;
|
||||
|
||||
-- 查看表结构示例
|
||||
-- DESCRIBE Users;
|
||||
-- DESCRIBE Orders;
|
||||
-- DESCRIBE OrderItems;
|
||||
|
||||
-- 测试查询示例
|
||||
-- SELECT u.username, COUNT(o.order_id) as order_count
|
||||
-- FROM Users u LEFT JOIN Orders o ON u.user_id = o.user_id
|
||||
-- GROUP BY u.user_id;
|
||||
|
||||
-- SELECT p.product_name, AVG(r.rating) as avg_rating
|
||||
-- FROM Products p LEFT JOIN Reviews r ON p.product_id = r.product_id
|
||||
-- GROUP BY p.product_id;
|
||||
|
||||
-- SELECT c.category_name, COUNT(p.product_id) as product_count
|
||||
-- FROM Categories c LEFT JOIN ProductCategories pc ON c.category_id = pc.category_id
|
||||
-- LEFT JOIN Products p ON pc.product_id = p.product_id
|
||||
-- GROUP BY c.category_id;
|
||||
```
|
||||
|
||||
## SQL文件说明
|
||||
|
||||
### 包含内容
|
||||
|
||||
| 组成部分 | 说明 |
|
||||
|---------|------|
|
||||
| **15张表** | Users、Categories、Products、ProductCategories、Orders、OrderItems、Addresses、Reviews、CartItems、ProductViews、Coupons、UserCoupons、Wishlists、Payments、Admins |
|
||||
| **复杂关联** | 一对多、多对多、自关联 |
|
||||
| **约束** | 主键、外键、UNIQUE、CHECK、DEFAULT |
|
||||
| **索引** | 常用查询字段均建立索引 |
|
||||
| **测试数据** | 每张表都包含实际数据 |
|
||||
| **迷惑项** | 按设计要求保留相似字段名、相近表名 |
|
||||
|
||||
### 表关联关系图
|
||||
|
||||
```
|
||||
Users (1) ----< (N) Orders (1) ----< (N) OrderItems (N) >---- (1) Products
|
||||
| | |
|
||||
| | |
|
||||
+-----< (N) Addresses +-----< (N) Payments +-----< (N) Reviews
|
||||
| |
|
||||
+-----< (N) Reviews |
|
||||
| |
|
||||
+-----< (N) CartItems +-----< (N) ProductViews
|
||||
| |
|
||||
+-----< (N) UserCoupons +----< (N) Coupons +-----< (N) Wishlists
|
||||
|
|
||||
+-----< (N) ProductCategories
|
||||
|
|
||||
Categories (1) ---------------------------------------------------+
|
||||
|
|
||||
+--< (N) Categories (自关联)
|
||||
```
|
||||
|
||||
### 使用方法
|
||||
|
||||
```bash
|
||||
# 在MySQL中执行
|
||||
mysql -u username -p < test_ecommerce_db.sql
|
||||
|
||||
# 或在MySQL客户端中执行
|
||||
source /path/to/test_ecommerce_db.sql
|
||||
```
|
||||
|
||||
此SQL文件可直接在MySQL 8.0+环境中执行,包含完整的表结构、约束、索引和测试数据。
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
-- =====================================================
|
||||
-- 数据库创建脚本
|
||||
-- 数据库名称:TestECommerceDB
|
||||
-- 用途:测试大语言模型的SQL编写能力
|
||||
-- 设计特点:复杂关联关系、一对多、多对多、自关联
|
||||
-- 迷惑项:相似字段名、相近表名、含义相近的字段
|
||||
-- =====================================================
|
||||
|
||||
-- 创建数据库
|
||||
DROP DATABASE IF EXISTS TestECommerceDB;
|
||||
CREATE DATABASE TestECommerceDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE TestECommerceDB;
|
||||
|
||||
-- =====================================================
|
||||
-- 1. 用户表 (Users)
|
||||
-- 存储用户基本信息
|
||||
-- 迷惑项:user_id、user_email、user_status 字段名相似
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Users (
|
||||
user_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
user_email VARCHAR(100) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(100),
|
||||
phone VARCHAR(20),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
user_status TINYINT DEFAULT 1 COMMENT '1=活跃, 0=禁用',
|
||||
last_login DATETIME,
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_user_email (user_email),
|
||||
INDEX idx_user_status (user_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 2. 分类表 (Categories)
|
||||
-- 存储产品分类信息
|
||||
-- 迷惑项:自关联关系,category_id、category_name、parent_id
|
||||
-- 特点:树形结构,支持多级分类
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Categories (
|
||||
category_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
category_name VARCHAR(50) NOT NULL,
|
||||
category_desc VARCHAR(200),
|
||||
parent_id INT DEFAULT NULL,
|
||||
category_order INT DEFAULT 0,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (parent_id) REFERENCES Categories(category_id) ON DELETE SET NULL,
|
||||
INDEX idx_parent_id (parent_id),
|
||||
INDEX idx_category_name (category_name),
|
||||
INDEX idx_is_active (is_active)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 3. 产品表 (Products)
|
||||
-- 存储产品信息
|
||||
-- 迷惑项:product_id、product_name、product_desc、product_status
|
||||
-- 含义相近字段:price vs cost_price
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Products (
|
||||
product_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
product_name VARCHAR(100) NOT NULL,
|
||||
product_desc TEXT,
|
||||
product_specs VARCHAR(255) COMMENT '产品规格JSON字符串',
|
||||
price DECIMAL(10,2) NOT NULL COMMENT '销售价格',
|
||||
cost_price DECIMAL(10,2) COMMENT '成本价格',
|
||||
stock_quantity INT DEFAULT 0,
|
||||
stock_alert_level INT DEFAULT 10,
|
||||
category_id INT,
|
||||
brand VARCHAR(50),
|
||||
product_status TINYINT DEFAULT 1 COMMENT '1=上架, 0=下架, 2=缺货',
|
||||
view_count INT DEFAULT 0,
|
||||
sales_count INT DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (category_id) REFERENCES Categories(category_id) ON DELETE SET NULL,
|
||||
INDEX idx_product_name (product_name),
|
||||
INDEX idx_price (price),
|
||||
INDEX idx_category_id (category_id),
|
||||
INDEX idx_product_status (product_status),
|
||||
INDEX idx_brand (brand),
|
||||
INDEX idx_sales_count (sales_count)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 4. 产品-分类关联表 (ProductCategories)
|
||||
-- 实现产品和分类的多对多关系
|
||||
-- 迷惑项:表名与Products、Categories相似
|
||||
-- 特点:支持产品属于多个分类,设置主分类
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE ProductCategories (
|
||||
product_id INT NOT NULL,
|
||||
category_id INT NOT NULL,
|
||||
is_primary TINYINT DEFAULT 0 COMMENT '1=主分类, 0=副分类',
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (product_id, category_id),
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (category_id) REFERENCES Categories(category_id) ON DELETE CASCADE,
|
||||
INDEX idx_category_id (category_id),
|
||||
INDEX idx_is_primary (is_primary)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 5. 订单表 (Orders)
|
||||
-- 存储订单信息
|
||||
-- 迷惑项:order_id、order_date、order_status
|
||||
-- 含义相近字段:total_amount
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Orders (
|
||||
order_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_number VARCHAR(30) NOT NULL UNIQUE COMMENT '订单编号',
|
||||
user_id INT NOT NULL,
|
||||
order_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
|
||||
discount_amount DECIMAL(10,2) DEFAULT 0 COMMENT '折扣金额',
|
||||
shipping_fee DECIMAL(10,2) DEFAULT 0 COMMENT '运费',
|
||||
payment_method VARCHAR(30) COMMENT '支付方式',
|
||||
payment_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/paid/failed/refunded',
|
||||
order_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/confirmshipped/delivered/cancelled/completed',
|
||||
shipping_addr_id INT,
|
||||
billing_addr_id INT,
|
||||
shipping_addr VARCHAR(500) COMMENT '收货地址快照',
|
||||
billing_addr VARCHAR(500) COMMENT '账单地址快照',
|
||||
customer_remark VARCHAR(500),
|
||||
internal_remark VARCHAR(500),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE RESTRICT,
|
||||
INDEX idx_order_number (order_number),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_order_date (order_date),
|
||||
INDEX idx_order_status (order_status),
|
||||
INDEX idx_payment_status (payment_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 6. 订单详情表 (OrderItems)
|
||||
-- 存储订单中每个产品的详细信息
|
||||
-- 迷惑项:order_item_id、order_id
|
||||
-- 含义相近字段:item_price vs price,item_total vs total_amount
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE OrderItems (
|
||||
order_item_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL,
|
||||
item_price DECIMAL(10,2) NOT NULL COMMENT '下单时的产品单价',
|
||||
item_cost DECIMAL(10,2) COMMENT '成本单价',
|
||||
item_discount DECIMAL(10,2) DEFAULT 0 COMMENT '该项折扣',
|
||||
item_total DECIMAL(10,2) NOT NULL COMMENT '该项总金额',
|
||||
item_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/shipped/delivered/returned',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE RESTRICT,
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_item_status (item_status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 7. 地址表 (Addresses)
|
||||
-- 存储用户的多个地址
|
||||
-- 迷惑项:address_id、address_line1、address_line2
|
||||
-- 一对多关系:一个用户有多个地址
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Addresses (
|
||||
address_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
address_label VARCHAR(50) COMMENT '地址标签,如:家、公司',
|
||||
recipient_name VARCHAR(100) NOT NULL,
|
||||
recipient_phone VARCHAR(20) NOT NULL,
|
||||
country VARCHAR(50) DEFAULT '中国',
|
||||
province VARCHAR(50),
|
||||
city VARCHAR(50) NOT NULL,
|
||||
district VARCHAR(50),
|
||||
address_line1 VARCHAR(200) NOT NULL,
|
||||
address_line2 VARCHAR(200),
|
||||
postal_code VARCHAR(20),
|
||||
address_type VARCHAR(20) DEFAULT 'shipping' COMMENT 'shipping/billing/both',
|
||||
is_default TINYINT DEFAULT 0,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_address_type (address_type),
|
||||
INDEX idx_is_default (is_default),
|
||||
INDEX idx_city (city)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 8. 评论表 (Reviews)
|
||||
-- 存储用户对产品的评论
|
||||
-- 迷惑项:review_id、review_text、review_date
|
||||
-- 多对一关系:评论关联用户和产品
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Reviews (
|
||||
review_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
order_id INT COMMENT '订单ID,可选',
|
||||
rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||
review_title VARCHAR(100),
|
||||
review_text TEXT,
|
||||
is_anonymous TINYINT DEFAULT 0,
|
||||
is_verified TINYINT DEFAULT 0 COMMENT '是否购买后评论',
|
||||
helpful_count INT DEFAULT 0,
|
||||
reply_count INT DEFAULT 0,
|
||||
review_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/approved/rejected',
|
||||
review_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE SET NULL,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_rating (rating),
|
||||
INDEX idx_review_status (review_status),
|
||||
INDEX idx_review_date (review_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 9. 购物车表 (CartItems)
|
||||
-- 存储用户当前购物车中的商品
|
||||
-- 迷惑项:cart_id、product_id、quantity
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE CartItems (
|
||||
cart_item_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL DEFAULT 1,
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_user_product (user_id, product_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 10. 产品浏览历史表 (ProductViews)
|
||||
-- 存储用户的浏览历史
|
||||
-- 迷惑项:view_id、product_id、view_date
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE ProductViews (
|
||||
view_id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT,
|
||||
product_id INT NOT NULL,
|
||||
view_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
session_id VARCHAR(100),
|
||||
view_duration INT DEFAULT 0 COMMENT '浏览时长(秒)',
|
||||
source_type VARCHAR(30) COMMENT '来源:search/category/recommend/direct',
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_view_date (view_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 11. 优惠券表 (Coupons)
|
||||
-- 存储优惠券信息
|
||||
-- 迷惑项:coupon_id、coupon_code、coupon_type
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Coupons (
|
||||
coupon_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
coupon_code VARCHAR(30) NOT NULL UNIQUE,
|
||||
coupon_name VARCHAR(100) NOT NULL,
|
||||
coupon_desc VARCHAR(255),
|
||||
coupon_type VARCHAR(20) DEFAULT 'percentage' COMMENT 'percentage/fixed',
|
||||
discount_value DECIMAL(10,2) NOT NULL COMMENT '折扣值(百分比或固定金额)',
|
||||
min_order_amount DECIMAL(10,2) DEFAULT 0,
|
||||
max_discount_amount DECIMAL(10,2) COMMENT '最大折扣金额',
|
||||
usage_limit INT COMMENT '总使用次数限制',
|
||||
usage_limit_per_user INT DEFAULT 1,
|
||||
usage_count INT DEFAULT 0,
|
||||
valid_from DATETIME NOT NULL,
|
||||
valid_until DATETIME NOT NULL,
|
||||
is_active TINYINT DEFAULT 1,
|
||||
applicable_product_ids TEXT COMMENT '适用的产品ID列表,JSON格式',
|
||||
applicable_category_ids TEXT COMMENT '适用的分类ID列表,JSON格式',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_coupon_code (coupon_code),
|
||||
INDEX idx_valid_dates (valid_from, valid_until),
|
||||
INDEX idx_is_active (is_active)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 12. 用户优惠券关联表 (UserCoupons)
|
||||
-- 存储用户领取的优惠券
|
||||
-- 迷惑项:user_coupon_id、user_id、coupon_id
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE UserCoupons (
|
||||
user_coupon_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
coupon_id INT NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'available' COMMENT 'available/used/expired',
|
||||
received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
used_at DATETIME,
|
||||
order_id INT,
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (coupon_id) REFERENCES Coupons(coupon_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE SET NULL,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_coupon_id (coupon_id),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 13. 产品收藏表 (Wishlists)
|
||||
-- 存储用户收藏的产品
|
||||
-- 迷惑项:wishlist_id、product_id、user_id
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Wishlists (
|
||||
wishlist_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
note VARCHAR(255),
|
||||
priority INT DEFAULT 0 COMMENT '优先级排序',
|
||||
FOREIGN KEY (user_id) REFERENCES Users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES Products(product_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uk_user_product (user_id, product_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_product_id (product_id),
|
||||
INDEX idx_added_at (added_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 14. 支付记录表 (Payments)
|
||||
-- 存储订单支付信息
|
||||
-- 迷惑项:payment_id、order_id、payment_amount
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Payments (
|
||||
payment_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_id INT NOT NULL,
|
||||
payment_method VARCHAR(30) NOT NULL,
|
||||
payment_amount DECIMAL(10,2) NOT NULL,
|
||||
transaction_id VARCHAR(100) UNIQUE,
|
||||
payment_status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/success/failed/refunded',
|
||||
payment_date DATETIME,
|
||||
refund_amount DECIMAL(10,2) DEFAULT 0,
|
||||
refund_reason VARCHAR(255),
|
||||
gateway_response TEXT COMMENT '支付网关响应',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES Orders(order_id) ON DELETE RESTRICT,
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_transaction_id (transaction_id),
|
||||
INDEX idx_payment_status (payment_status),
|
||||
INDEX idx_payment_date (payment_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 15. 管理员表 (Admins)
|
||||
-- 存储管理员信息
|
||||
-- 迷惑项:admin_id、admin_email、admin_role
|
||||
-- =====================================================
|
||||
|
||||
CREATE TABLE Admins (
|
||||
admin_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
admin_email VARCHAR(100) NOT NULL UNIQUE,
|
||||
admin_name VARCHAR(50) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
admin_role VARCHAR(30) DEFAULT 'staff' COMMENT 'super_admin/admin/staff',
|
||||
is_active TINYINT DEFAULT 1,
|
||||
last_login DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_admin_email (admin_email),
|
||||
INDEX idx_admin_role (admin_role)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- =====================================================
|
||||
-- 添加外键约束到Orders表的shipping_addr_id和billing_addr_id
|
||||
-- =====================================================
|
||||
|
||||
ALTER TABLE Orders
|
||||
ADD CONSTRAINT fk_orders_shipping_addr
|
||||
FOREIGN KEY (shipping_addr_id) REFERENCES Addresses(address_id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE Orders
|
||||
ADD CONSTRAINT fk_orders_billing_addr
|
||||
FOREIGN KEY (billing_addr_id) REFERENCES Addresses(address_id) ON DELETE SET NULL;
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
CREATE DATABASE ecommerce_test;
|
||||
USE ecommerce_test;
|
||||
|
||||
CREATE TABLE users (
|
||||
user_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
user_name VARCHAR(50),
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
phone VARCHAR(20),
|
||||
registration_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
user_type ENUM('regular', 'vip', 'admin') DEFAULT 'regular',
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE products (
|
||||
product_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_name VARCHAR(200) NOT NULL,
|
||||
prod_name VARCHAR(200),
|
||||
sku VARCHAR(50) UNIQUE,
|
||||
product_code VARCHAR(50) UNIQUE,
|
||||
description TEXT,
|
||||
prod_desc TEXT,
|
||||
price DECIMAL(10,2) NOT NULL,
|
||||
cost_price DECIMAL(10,2),
|
||||
original_price DECIMAL(10,2),
|
||||
brand VARCHAR(100),
|
||||
weight DECIMAL(8,3),
|
||||
size VARCHAR(50),
|
||||
color VARCHAR(50),
|
||||
status ENUM('active', 'inactive', 'discontinued') DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE categories (
|
||||
category_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
category_name VARCHAR(100) NOT NULL,
|
||||
cat_name VARCHAR(100),
|
||||
parent_category_id INT,
|
||||
category_level TINYINT DEFAULT 1,
|
||||
category_code VARCHAR(50),
|
||||
description TEXT,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
sort_order INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (parent_category_id) REFERENCES categories(category_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE product_category_map (
|
||||
map_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
category_id INT NOT NULL,
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
assigned_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_product_category (product_id, category_id)
|
||||
);
|
||||
|
||||
CREATE TABLE inventory (
|
||||
inventory_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
warehouse_location VARCHAR(100),
|
||||
stock_quantity INT DEFAULT 0,
|
||||
stock_num INT DEFAULT 0,
|
||||
stock_count INT DEFAULT 0,
|
||||
reserved_quantity INT DEFAULT 0,
|
||||
available_quantity INT DEFAULT 0,
|
||||
min_stock_level INT DEFAULT 0,
|
||||
max_stock_level INT DEFAULT 1000,
|
||||
last_restock_date DATETIME,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_product_warehouse (product_id, warehouse_location)
|
||||
);
|
||||
|
||||
CREATE TABLE orders (
|
||||
order_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
order_no VARCHAR(50) UNIQUE,
|
||||
order_code VARCHAR(50),
|
||||
user_id INT NOT NULL,
|
||||
order_status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled', 'refunded') DEFAULT 'pending',
|
||||
total_amount DECIMAL(10,2) NOT NULL,
|
||||
discount_amount DECIMAL(10,2) DEFAULT 0,
|
||||
final_amount DECIMAL(10,2) NOT NULL,
|
||||
shipping_address TEXT,
|
||||
billing_address TEXT,
|
||||
payment_method VARCHAR(50),
|
||||
payment_status ENUM('unpaid', 'paid', 'refunded') DEFAULT 'unpaid',
|
||||
order_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
shipped_date DATETIME,
|
||||
delivered_date DATETIME,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
CREATE TABLE order_items (
|
||||
item_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL,
|
||||
unit_price DECIMAL(10,2) NOT NULL,
|
||||
total_price DECIMAL(10,2) NOT NULL,
|
||||
discount_rate DECIMAL(5,2) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
CREATE TABLE shopping_cart (
|
||||
cart_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL DEFAULT 1,
|
||||
added_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_user_product (user_id, product_id)
|
||||
);
|
||||
|
||||
CREATE TABLE user_addresses (
|
||||
address_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT NOT NULL,
|
||||
address_type ENUM('home', 'work', 'other') DEFAULT 'home',
|
||||
recipient_name VARCHAR(100) NOT NULL,
|
||||
recipient_phone VARCHAR(20) NOT NULL,
|
||||
province VARCHAR(50) NOT NULL,
|
||||
city VARCHAR(50) NOT NULL,
|
||||
district VARCHAR(50),
|
||||
street_address TEXT NOT NULL,
|
||||
postal_code VARCHAR(10),
|
||||
is_default BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE promotions (
|
||||
promotion_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
promotion_name VARCHAR(200) NOT NULL,
|
||||
promo_name VARCHAR(200),
|
||||
promotion_type ENUM('percentage', 'fixed_amount', 'buy_x_get_y') NOT NULL,
|
||||
promo_value DECIMAL(10,2) NOT NULL,
|
||||
start_date DATETIME NOT NULL,
|
||||
end_date DATETIME NOT NULL,
|
||||
min_order_amount DECIMAL(10,2) DEFAULT 0,
|
||||
max_discount_amount DECIMAL(10,2),
|
||||
usage_limit INT,
|
||||
used_count INT DEFAULT 0,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE order_promotions (
|
||||
order_promo_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id INT NOT NULL,
|
||||
promotion_id INT NOT NULL,
|
||||
discount_amount DECIMAL(10,2) NOT NULL,
|
||||
applied_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (promotion_id) REFERENCES promotions(promotion_id) ON DELETE RESTRICT,
|
||||
UNIQUE KEY unique_order_promotion (order_id, promotion_id)
|
||||
);
|
||||
|
||||
CREATE TABLE product_reviews (
|
||||
review_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
order_id INT,
|
||||
rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||
review_title VARCHAR(200),
|
||||
review_content TEXT,
|
||||
helpful_count INT DEFAULT 0,
|
||||
is_verified_purchase BOOLEAN DEFAULT FALSE,
|
||||
review_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE SET NULL,
|
||||
UNIQUE KEY unique_user_product_review (user_id, product_id, order_id)
|
||||
);
|
||||
|
||||
CREATE TABLE suppliers (
|
||||
supplier_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
supplier_name VARCHAR(200) NOT NULL,
|
||||
vendor_name VARCHAR(200),
|
||||
contact_person VARCHAR(100),
|
||||
contact_phone VARCHAR(20),
|
||||
contact_email VARCHAR(100),
|
||||
supplier_address TEXT,
|
||||
payment_terms VARCHAR(100),
|
||||
credit_limit DECIMAL(12,2),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE product_suppliers (
|
||||
product_supplier_id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
product_id INT NOT NULL,
|
||||
supplier_id INT NOT NULL,
|
||||
supplier_price DECIMAL(10,2) NOT NULL,
|
||||
lead_time_days INT DEFAULT 7,
|
||||
is_primary_supplier BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (supplier_id) REFERENCES suppliers(supplier_id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_product_supplier (product_id, supplier_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_products_name ON products(product_name);
|
||||
CREATE INDEX idx_products_brand ON products(brand);
|
||||
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date);
|
||||
CREATE INDEX idx_orders_status ON orders(order_status);
|
||||
CREATE INDEX idx_inventory_product ON inventory(product_id);
|
||||
CREATE INDEX idx_cart_user ON shopping_cart(user_id);
|
||||
CREATE INDEX idx_reviews_product_rating ON product_reviews(product_id, rating);
|
||||
Loading…
Reference in New Issue