稳定提交
This commit is contained in:
parent
3d11c02067
commit
431888797d
11
package.json
11
package.json
|
@ -19,7 +19,13 @@
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@codemirror/view": "^6.36.4",
|
"@codemirror/view": "^6.36.4",
|
||||||
"@element-plus/icons-vue": "^2.3.0",
|
"@element-plus/icons-vue": "^2.3.0",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
|
"@fortawesome/vue-fontawesome": "^3.0.8",
|
||||||
|
"@iconify/vue": "^4.3.0",
|
||||||
"@monaco-editor/loader": "^1.5.0",
|
"@monaco-editor/loader": "^1.5.0",
|
||||||
|
"@vicons/ionicons5": "^0.13.0",
|
||||||
|
"@vueuse/core": "^13.0.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"dayjs": "^1.11.0",
|
"dayjs": "^1.11.0",
|
||||||
|
@ -28,13 +34,18 @@
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"monaco-editor": "^0.30.1",
|
"monaco-editor": "^0.30.1",
|
||||||
|
"naive-ui": "^2.41.0",
|
||||||
"pinia": "^2.1.0",
|
"pinia": "^2.1.0",
|
||||||
|
"qs": "^6.14.0",
|
||||||
"three": "^0.160.0",
|
"three": "^0.160.0",
|
||||||
|
"vfonts": "^0.0.3",
|
||||||
|
"vtron": "^0.7.8",
|
||||||
"vue": "^3.4.0",
|
"vue": "^3.4.0",
|
||||||
"vue-codemirror": "^6.1.1",
|
"vue-codemirror": "^6.1.1",
|
||||||
"vue-echarts": "^6.6.0",
|
"vue-echarts": "^6.6.0",
|
||||||
"vue-monaco-editor": "^0.0.19",
|
"vue-monaco-editor": "^0.0.19",
|
||||||
"vue-router": "^4.2.0",
|
"vue-router": "^4.2.0",
|
||||||
|
"vue-windows": "^0.3.0",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -6,6 +6,7 @@ export function getCourses(params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCoursesMethod(params) {
|
export function getCoursesMethod(params) {
|
||||||
|
console.log(params)
|
||||||
return get('/bs/courses', params)
|
return get('/bs/courses', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { post } from '@/utils/request'
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
export function fetchDetail(id) {
|
export function fetchDetail(id) {
|
||||||
return post('/exam/api/exam/exam/detail', { id: id })
|
return post('/api/common/exam/api/exam/exam/detail', { id: id })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { get, post } from '@/utils/request'
|
||||||
|
|
||||||
|
// 获取用户积分
|
||||||
|
export function getUserPoints() {
|
||||||
|
return get('/api/shop/points')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取积分历史
|
||||||
|
export function getPointsHistory() {
|
||||||
|
return get('/api/shop/points/history')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取商品列表
|
||||||
|
export function getGoodsList() {
|
||||||
|
return get('/api/shop/goods')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兑换商品
|
||||||
|
export function exchangeGoods(goodsId) {
|
||||||
|
return post('/api/shop/exchange', { goodsId })
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import {postRequest, post, get, postRequestJSON, getRequest, getRequestWithqs} from "@/utils/request.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const testPostUseParamMethod = (param) => {
|
||||||
|
return postRequest("/api/common/testPostUseParam", param)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testPostUseBodyMethod = (data) =>{
|
||||||
|
return post("/api/common/testPostUseBody", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testGetParamMethod = (param) =>{
|
||||||
|
return get("/api/common/testGetParam", param)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testGetParamMethodList = (param) =>{
|
||||||
|
return getRequest("/api/common/testGetParam", param)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testGetParamMethodqs = (param) =>{
|
||||||
|
return getRequestWithqs("/api/common/testGetParam", param)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,393 @@
|
||||||
|
<template>
|
||||||
|
<div class="debugger-container">
|
||||||
|
<div class="debugger-toolbar">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type="primary" size="small" @click="startDebug" :disabled="isDebugging">
|
||||||
|
<el-icon><VideoPlay /></el-icon> 开始调试
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="continueExecution" :disabled="!isDebugging">
|
||||||
|
<el-icon><Right /></el-icon> 继续
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="stepOver" :disabled="!isDebugging">
|
||||||
|
<el-icon><Bottom /></el-icon> 单步跳过
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="stepInto" :disabled="!isDebugging">
|
||||||
|
<el-icon><TopRight /></el-icon> 单步进入
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="stepOut" :disabled="!isDebugging">
|
||||||
|
<el-icon><TopLeft /></el-icon> 单步跳出
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" size="small" @click="stopDebug" :disabled="!isDebugging">
|
||||||
|
<el-icon><VideoPause /></el-icon> 停止调试
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="debugger-content">
|
||||||
|
<div class="debugger-code">
|
||||||
|
<code-editor
|
||||||
|
v-model="code"
|
||||||
|
:language="language"
|
||||||
|
:readOnly="isDebugging"
|
||||||
|
height="400px"
|
||||||
|
@line-click="toggleBreakpoint"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-for="(bp, index) in breakpoints"
|
||||||
|
:key="index"
|
||||||
|
class="breakpoint"
|
||||||
|
:style="{ top: (bp.line * 18) + 'px' }"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-if="currentLine"
|
||||||
|
class="current-line"
|
||||||
|
:style="{ top: (currentLine * 18) + 'px' }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="debugger-panels">
|
||||||
|
<el-tabs v-model="activePanel">
|
||||||
|
<el-tab-pane label="控制台" name="console">
|
||||||
|
<div class="console-output" ref="consoleOutputEl">
|
||||||
|
<div v-for="(line, index) in consoleOutput" :key="index" class="console-line">
|
||||||
|
{{ line }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="console-input">
|
||||||
|
<el-input
|
||||||
|
v-model="consoleInput"
|
||||||
|
placeholder="输入GDB命令..."
|
||||||
|
@keyup.enter="executeCommand"
|
||||||
|
:disabled="!isDebugging"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="executeCommand" :disabled="!isDebugging">执行</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="变量" name="variables">
|
||||||
|
<el-table :data="variables" style="width: 100%">
|
||||||
|
<el-table-column prop="name" label="名称" />
|
||||||
|
<el-table-column prop="value" label="值" />
|
||||||
|
<el-table-column prop="type" label="类型" />
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="调用栈" name="callstack">
|
||||||
|
<el-table :data="callstack" style="width: 100%">
|
||||||
|
<el-table-column prop="level" label="层级" width="60" />
|
||||||
|
<el-table-column prop="function" label="函数" />
|
||||||
|
<el-table-column prop="location" label="位置" />
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, reactive, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { VideoPlay, VideoPause, Right, Bottom, TopRight, TopLeft } from '@element-plus/icons-vue'
|
||||||
|
import CodeEditor from '@/components/CodeEditor/index.vue'
|
||||||
|
import { startDebugSession, executeDebugCommand, getDebugStatus, endDebugSession } from '@/api/exam/exam'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Debugger',
|
||||||
|
components: {
|
||||||
|
CodeEditor,
|
||||||
|
VideoPlay, VideoPause, Right, Bottom, TopRight, TopLeft
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
default: 'cpp'
|
||||||
|
},
|
||||||
|
problemId: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const code = ref(props.modelValue)
|
||||||
|
const isDebugging = ref(false)
|
||||||
|
const sessionId = ref(null)
|
||||||
|
const breakpoints = ref([])
|
||||||
|
const currentLine = ref(null)
|
||||||
|
const consoleOutput = ref([])
|
||||||
|
const consoleInput = ref('')
|
||||||
|
const activePanel = ref('console')
|
||||||
|
const variables = ref([])
|
||||||
|
const callstack = ref([])
|
||||||
|
const consoleOutputEl = ref(null)
|
||||||
|
const statusInterval = ref(null)
|
||||||
|
|
||||||
|
// 监听代码变化
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
if (code.value !== newVal) {
|
||||||
|
code.value = newVal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => code.value, (newVal) => {
|
||||||
|
if (props.modelValue !== newVal) {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 开始调试
|
||||||
|
const startDebug = async () => {
|
||||||
|
try {
|
||||||
|
const response = await startDebugSession({
|
||||||
|
code: code.value,
|
||||||
|
language: props.language,
|
||||||
|
problemId: props.problemId,
|
||||||
|
breakpoints: breakpoints.value.map(bp => bp.line)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
sessionId.value = response.data.sessionId
|
||||||
|
isDebugging.value = true
|
||||||
|
consoleOutput.value = ['调试会话已启动...']
|
||||||
|
|
||||||
|
// 开始定期获取调试状态
|
||||||
|
statusInterval.value = setInterval(updateDebugStatus, 1000)
|
||||||
|
|
||||||
|
ElMessage.success('调试会话已启动')
|
||||||
|
} else {
|
||||||
|
ElMessage.error(response.msg || '启动调试失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启动调试出错:', error)
|
||||||
|
ElMessage.error('启动调试出错')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止调试
|
||||||
|
const stopDebug = async () => {
|
||||||
|
if (!sessionId.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await endDebugSession(sessionId.value)
|
||||||
|
clearInterval(statusInterval.value)
|
||||||
|
isDebugging.value = false
|
||||||
|
sessionId.value = null
|
||||||
|
currentLine.value = null
|
||||||
|
variables.value = []
|
||||||
|
callstack.value = []
|
||||||
|
consoleOutput.value.push('调试会话已结束')
|
||||||
|
ElMessage.info('调试会话已结束')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('结束调试出错:', error)
|
||||||
|
ElMessage.error('结束调试出错')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行GDB命令
|
||||||
|
const executeCommand = async () => {
|
||||||
|
if (!sessionId.value || !consoleInput.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
consoleOutput.value.push(`> ${consoleInput.value}`)
|
||||||
|
|
||||||
|
const response = await executeDebugCommand(sessionId.value, consoleInput.value)
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
if (response.data.output) {
|
||||||
|
consoleOutput.value.push(response.data.output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新调试状态
|
||||||
|
updateDebugStatus()
|
||||||
|
} else {
|
||||||
|
consoleOutput.value.push(`错误: ${response.msg || '命令执行失败'}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleInput.value = ''
|
||||||
|
|
||||||
|
// 滚动到控制台底部
|
||||||
|
scrollToBottom()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('执行命令出错:', error)
|
||||||
|
consoleOutput.value.push(`错误: ${error.message || '命令执行出错'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新调试状态
|
||||||
|
const updateDebugStatus = async () => {
|
||||||
|
if (!sessionId.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await getDebugStatus(sessionId.value)
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
const { currentLine: line, variables: vars, callstack: stack } = response.data
|
||||||
|
|
||||||
|
currentLine.value = line
|
||||||
|
variables.value = vars || []
|
||||||
|
callstack.value = stack || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取调试状态出错:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换断点
|
||||||
|
const toggleBreakpoint = (line) => {
|
||||||
|
const index = breakpoints.value.findIndex(bp => bp.line === line)
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
breakpoints.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
breakpoints.value.push({ line })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在调试,更新断点
|
||||||
|
if (isDebugging.value) {
|
||||||
|
executeCommand(`break ${line}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续执行
|
||||||
|
const continueExecution = () => {
|
||||||
|
executeCommand('continue')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单步跳过
|
||||||
|
const stepOver = () => {
|
||||||
|
executeCommand('next')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单步进入
|
||||||
|
const stepInto = () => {
|
||||||
|
executeCommand('step')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单步跳出
|
||||||
|
const stepOut = () => {
|
||||||
|
executeCommand('finish')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动到控制台底部
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
if (consoleOutputEl.value) {
|
||||||
|
consoleOutputEl.value.scrollTop = consoleOutputEl.value.scrollHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件卸载前清理
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (statusInterval.value) {
|
||||||
|
clearInterval(statusInterval.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionId.value) {
|
||||||
|
endDebugSession(sessionId.value).catch(console.error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
isDebugging,
|
||||||
|
breakpoints,
|
||||||
|
currentLine,
|
||||||
|
consoleOutput,
|
||||||
|
consoleOutputEl,
|
||||||
|
consoleInput,
|
||||||
|
activePanel,
|
||||||
|
variables,
|
||||||
|
callstack,
|
||||||
|
startDebug,
|
||||||
|
stopDebug,
|
||||||
|
executeCommand,
|
||||||
|
toggleBreakpoint,
|
||||||
|
continueExecution,
|
||||||
|
stepOver,
|
||||||
|
stepInto,
|
||||||
|
stepOut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.debugger-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid #dcdfe6;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugger-toolbar {
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid #dcdfe6;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugger-content {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugger-code {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border-right: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakpoint {
|
||||||
|
position: absolute;
|
||||||
|
left: 3px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #f56c6c;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 18px;
|
||||||
|
background-color: rgba(64, 158, 255, 0.1);
|
||||||
|
border-left: 2px solid #409eff;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debugger-panels {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-output {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-line {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-input {
|
||||||
|
padding: 8px;
|
||||||
|
border-top: 1px solid #dcdfe6;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,236 @@
|
||||||
|
<template>
|
||||||
|
<div class="echarts-wrapper">
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="loading" class="loading-mask">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref="chartRef"
|
||||||
|
:style="{
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
minHeight: '100px'
|
||||||
|
}"
|
||||||
|
class="echarts-container"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
|
||||||
|
import * as echarts from 'echarts'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EChartsComponent',
|
||||||
|
props: {
|
||||||
|
// 图表配置项
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// 图表主题
|
||||||
|
theme: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 图表宽度
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
// 图表高度
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '400px'
|
||||||
|
},
|
||||||
|
// 是否自动调整大小
|
||||||
|
autoResize: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 加载状态
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 动画时长
|
||||||
|
animationDuration: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
},
|
||||||
|
// 是否平滑曲线(用于折线图)
|
||||||
|
smooth: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 图表类型
|
||||||
|
chartType: {
|
||||||
|
type: String,
|
||||||
|
default: 'line',
|
||||||
|
validator: (value) => ['line', 'bar', 'pie', 'scatter'].includes(value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['chartReady', 'click', 'legendselectchanged'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const chartRef = ref(null)
|
||||||
|
let chartInstance = null
|
||||||
|
|
||||||
|
// 处理配置项
|
||||||
|
const processOptions = (options) => {
|
||||||
|
const defaultOptions = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
right: '3%',
|
||||||
|
left: '3%',
|
||||||
|
bottom: '3%',
|
||||||
|
top: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
animation: true,
|
||||||
|
animationDuration: props.animationDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理折线图的平滑设置
|
||||||
|
if (props.chartType === 'line' && options.series) {
|
||||||
|
options.series = options.series.map(series => ({
|
||||||
|
...series,
|
||||||
|
smooth: props.smooth
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...defaultOptions,
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value, props.theme)
|
||||||
|
|
||||||
|
// 绑定事件
|
||||||
|
chartInstance.on('click', (params) => {
|
||||||
|
emit('click', params)
|
||||||
|
})
|
||||||
|
|
||||||
|
chartInstance.on('legendselectchanged', (params) => {
|
||||||
|
emit('legendselectchanged', params)
|
||||||
|
})
|
||||||
|
|
||||||
|
const processedOptions = processOptions(props.options)
|
||||||
|
chartInstance.setOption(processedOptions)
|
||||||
|
emit('chartReady', chartInstance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图表
|
||||||
|
const updateChart = () => {
|
||||||
|
if (!chartInstance) return
|
||||||
|
|
||||||
|
const processedOptions = processOptions(props.options)
|
||||||
|
chartInstance.setOption(processedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调整图表大小
|
||||||
|
const resizeChart = debounce(() => {
|
||||||
|
if (!chartInstance) return
|
||||||
|
chartInstance.resize()
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
// 监听配置变化
|
||||||
|
watch(
|
||||||
|
() => props.options,
|
||||||
|
() => updateChart(),
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听主题变化
|
||||||
|
watch(
|
||||||
|
() => props.theme,
|
||||||
|
() => {
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose()
|
||||||
|
}
|
||||||
|
initChart()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听加载状态
|
||||||
|
watch(
|
||||||
|
() => props.loading,
|
||||||
|
(val) => {
|
||||||
|
if (chartInstance) {
|
||||||
|
val ? chartInstance.showLoading() : chartInstance.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initChart()
|
||||||
|
if (props.autoResize) {
|
||||||
|
window.addEventListener('resize', resizeChart)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (props.autoResize) {
|
||||||
|
window.removeEventListener('resize', resizeChart)
|
||||||
|
}
|
||||||
|
if (chartInstance) {
|
||||||
|
chartInstance.dispose()
|
||||||
|
chartInstance = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartRef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.echarts-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.echarts-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,9 +6,7 @@ import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import './styles/element.scss'
|
import './styles/element.scss'
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
// 暂时注释掉这两行,直到我们创建了相应的文件
|
import 'vtron/distlib/style.css';
|
||||||
// import 'animate.css'
|
|
||||||
// import './assets/styles/main.scss'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
@ -21,4 +19,4 @@ app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(ElementPlus)
|
app.use(ElementPlus)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
|
@ -96,6 +96,11 @@ const routes = [
|
||||||
name: 'ActivityCenter',
|
name: 'ActivityCenter',
|
||||||
component: () => import('../views/activity/ActivityCenter.vue')
|
component: () => import('../views/activity/ActivityCenter.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/test',
|
||||||
|
name: 'TestCenter',
|
||||||
|
component: () => import('../views/activity/TestCenter.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/mall',
|
path: '/mall',
|
||||||
name: 'Mall',
|
name: 'Mall',
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { useUserStore } from '../stores/user'
|
import { useUserStore } from '../stores/user'
|
||||||
|
import qs from 'qs';
|
||||||
|
|
||||||
// 创建 axios 实例
|
// 创建 axios 实例
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
// baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量获取 API 基础 URL
|
// baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量获取 API 基础 URL
|
||||||
//baseURL: 'http://localhost:8101', // 从环境变量获取 API 基础 URL
|
//baseURL: 'http://localhost:8101', // 从环境变量获取 API 基础 URL
|
||||||
baseURL: 'http://localhost:8101', // 从环境变量获取 API 基础 URL
|
baseURL: 'http://localhost:8084', // 从环境变量获取 API 基础 URL
|
||||||
timeout: 15000, // 请求超时时间
|
timeout: 15000, // 请求超时时间
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -206,6 +207,65 @@ export function del(url, params) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 封装 `@RequestParam` 方式的 POST 请求
|
||||||
|
export function postRequest(url, params) {
|
||||||
|
const formData = new FormData();
|
||||||
|
for (const key in params) {
|
||||||
|
console.log(params[key]);
|
||||||
|
formData.append(key, params[key]);
|
||||||
|
}
|
||||||
|
for (let pair of formData.entries()) {
|
||||||
|
console.log(pair[0] + ': ' + pair[1]);
|
||||||
|
}
|
||||||
|
return service({
|
||||||
|
url,
|
||||||
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getRequest(url, params) {
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
for (const key in params) {
|
||||||
|
if (Array.isArray(params[key])) {
|
||||||
|
params[key].forEach(item => {
|
||||||
|
searchParams.append(key, item); // 注意这里直接使用 key,不加 []
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
searchParams.append(key, params[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return service({
|
||||||
|
url: `${url}?${searchParams.toString()}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getRequestWithqs(url, params) {
|
||||||
|
return service({
|
||||||
|
url,
|
||||||
|
method: 'get',
|
||||||
|
params,
|
||||||
|
paramsSerializer: params => {
|
||||||
|
// 设置 arrayFormat 为 'repeat' 表示 ids=1&ids=2&ids=3
|
||||||
|
// 设置 arrayFormat 为 'comma' 表示 ds=1%2C2%2C3
|
||||||
|
return qs.stringify(params, { arrayFormat: 'repeat' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function postRequestJSON(url, params) {
|
||||||
|
return service({
|
||||||
|
url,
|
||||||
|
method: 'post',
|
||||||
|
data: JSON.stringify(params), // 发送 JSON
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 封装上传文件的请求
|
// 封装上传文件的请求
|
||||||
export function upload(url, file, onUploadProgress) {
|
export function upload(url, file, onUploadProgress) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
|
@ -170,11 +170,18 @@ const features = [
|
||||||
|
|
||||||
const popularCourses = ref([])
|
const popularCourses = ref([])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getCourses = async () => {
|
const getCourses = async () => {
|
||||||
var axiosResponse = await getCoursesMethod();
|
|
||||||
|
const params = {
|
||||||
|
page: 1, // 当前页码
|
||||||
|
size: 4, // 每页数量
|
||||||
|
};
|
||||||
|
|
||||||
|
var axiosResponse = await getCoursesMethod(params);
|
||||||
console.log(axiosResponse)
|
console.log(axiosResponse)
|
||||||
popularCourses.value = axiosResponse.data
|
popularCourses.value = axiosResponse.data
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewCourse = (courseId) => {
|
const viewCourse = (courseId) => {
|
||||||
|
|
|
@ -1,11 +1,174 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="activity-center">
|
<el-tree-select
|
||||||
<h1 class="page-title">活动大厅</h1>
|
v-model="selectedValue"
|
||||||
<p>活动大厅功能正在开发中,敬请期待...</p>
|
:data="treeData"
|
||||||
</div>
|
:props="treeProps"
|
||||||
|
show-checkbox
|
||||||
|
@check="handleCheck"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {
|
||||||
|
testGetParamMethod,
|
||||||
|
testGetParamMethodList,
|
||||||
|
testGetParamMethodqs,
|
||||||
|
testPostUseBodyMethod,
|
||||||
|
testPostUseParamMethod
|
||||||
|
} from "@/api/test.js";
|
||||||
|
import { ref, reactive, getCurrentInstance, nextTick, onMounted} from 'vue';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(111)
|
||||||
|
// 方式1 post方法 @RequestParam("ids") List<String> ids
|
||||||
|
// 可以
|
||||||
|
// Spring 内部有个转换机制,会自动把逗号分隔的字符串拆分成 List
|
||||||
|
/* testPostUseParamMethod({
|
||||||
|
ids: "122ss,22s2,3ss11aa",
|
||||||
|
str: 1
|
||||||
|
})*/
|
||||||
|
//可以
|
||||||
|
// 当你直接将数组作为一个值传入 FormData 或 URLSearchParams 的append 方法时,
|
||||||
|
// JavaScript 会自动将数组转换为字符串,默认行为是调用数组的 toString() 方法,
|
||||||
|
// 这个方法会将数组元素以逗号分隔拼接成一个字符串。
|
||||||
|
|
||||||
|
//方式二 post方法 @RequestBody 实体内部 List<String> ids;
|
||||||
|
|
||||||
|
//可以
|
||||||
|
// testPostUseParamMethod({
|
||||||
|
// ids: [1, 2, 3],
|
||||||
|
// str: 1
|
||||||
|
// })
|
||||||
|
//可以
|
||||||
|
// testPostUseBodyMethod({
|
||||||
|
// ids: [1, 2, 3],
|
||||||
|
// str: 1
|
||||||
|
// })
|
||||||
|
// 不可以
|
||||||
|
// 在反序列化 JSON 时,试图将一个字符串(例如:"122ss,22s2,3ss11aa")转换成一个 ArrayList 类型,
|
||||||
|
// 但它找不到合适的构造函数或工厂方法来从单个字符串创建一个 List 对象。
|
||||||
|
/* testPostUseBodyMethod({
|
||||||
|
ids: "122ss,22s2,3ss11aa",
|
||||||
|
str: 1
|
||||||
|
})*/
|
||||||
|
// get 方法 @RequestParam("ids") List<String> ids
|
||||||
|
// ids = 122ss,22s2,3ss11a
|
||||||
|
testGetParamMethod({
|
||||||
|
ids: "122ss,22s2,3ss11aa",
|
||||||
|
str: 1
|
||||||
|
})
|
||||||
|
//不行会变成 ids[]=1&ids[]=2&ids[]=3&str=1
|
||||||
|
/* testGetParamMethod({
|
||||||
|
ids: [1, 2, 3],
|
||||||
|
str: 1
|
||||||
|
})*/
|
||||||
|
|
||||||
|
//手动处理
|
||||||
|
//ids=1&ids=2&ids=3
|
||||||
|
testGetParamMethodList({
|
||||||
|
ids: [1, 2, 3],
|
||||||
|
str: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
//ids=1&ids=2&ids=3
|
||||||
|
testGetParamMethodqs({
|
||||||
|
ids: [1, 2, 3],
|
||||||
|
str: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
// 树形数据
|
||||||
|
const treeData = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
label: '父级1',
|
||||||
|
children: [
|
||||||
|
{ id: 11, label: '子级1-1' },
|
||||||
|
{ id: 12, label: '子级1-2' },
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
label: '子级1-3',
|
||||||
|
children: [
|
||||||
|
{ id: 131, label: '子子级1-3-1' },
|
||||||
|
{ id: 132, label: '子子级1-3-2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
label: '父级2',
|
||||||
|
children: [
|
||||||
|
{ id: 21, label: '子级2-1' },
|
||||||
|
{ id: 22, label: '子级2-2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 树形选择的配置属性
|
||||||
|
const treeProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'label',
|
||||||
|
children: 'children',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前选中的值
|
||||||
|
const selectedValue = ref(null);
|
||||||
|
|
||||||
|
// 处理节点选中事件
|
||||||
|
const handleCheck = (currentNode, checkedNodes) => {
|
||||||
|
// 获取所有选中的节点
|
||||||
|
const checked = checkedNodes.checkedNodes;
|
||||||
|
|
||||||
|
// 用于存储最终的叶子节点id
|
||||||
|
const finalIds = new Set(); // 使用Set避免重复
|
||||||
|
|
||||||
|
checked.forEach(node => {
|
||||||
|
// 判断是否是叶子节点(即没有子节点)
|
||||||
|
if (!node.children || node.children.length === 0) {
|
||||||
|
//在这 可以 node.属性 去个性化判断
|
||||||
|
finalIds.add(node.id);
|
||||||
|
} else {
|
||||||
|
// 如果是父级节点,递归获取其下的叶子节点id
|
||||||
|
const leafIds = getLeafIds(node, new Set());
|
||||||
|
leafIds.forEach(id => finalIds.add(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('最终的叶子节点id列表:', Array.from(finalIds));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 递归获取叶子节点id
|
||||||
|
const getLeafIds = (node, visited) => {
|
||||||
|
const ids = new Set();
|
||||||
|
if (node.children) {
|
||||||
|
node.children.forEach(child => {
|
||||||
|
if (!child.children || child.children.length === 0) {
|
||||||
|
//同理这里 //在这 可以 child.属性 去个性化判断
|
||||||
|
// 如果是叶子节点,添加其id
|
||||||
|
ids.add(child.id);
|
||||||
|
} else {
|
||||||
|
// 如果不是叶子节点,继续递归
|
||||||
|
if (!visited.has(child.id)) {
|
||||||
|
visited.add(child.id);
|
||||||
|
const childIds = getLeafIds(child, visited);
|
||||||
|
childIds.forEach(id => ids.add(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -13,4 +176,4 @@
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,462 @@
|
||||||
|
<template>
|
||||||
|
<div class="test-center">
|
||||||
|
<!-- 第一行搜索区域 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="selectedValue"
|
||||||
|
:data="treeData"
|
||||||
|
:props="treeProps"
|
||||||
|
show-checkbox
|
||||||
|
@check="handleCheck"
|
||||||
|
style="width: 600px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二行操作区域 -->
|
||||||
|
<div class="operation-section">
|
||||||
|
<div class="left-area">
|
||||||
|
<el-select v-model="value" placeholder="Select" style="width: 240px">
|
||||||
|
<el-option
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
:disabled="item.disabled"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-tree-select
|
||||||
|
v-model="selectedValue"
|
||||||
|
:data="treeData"
|
||||||
|
:props="treeProps"
|
||||||
|
show-checkbox
|
||||||
|
@check="handleCheck"
|
||||||
|
style="width: 600px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="right-area">
|
||||||
|
<el-button type="primary" disabled>操作1</el-button>
|
||||||
|
<el-button type="success" disabled>操作2</el-button>
|
||||||
|
<el-button type="warning" disabled>操作3</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<div class="table-section">
|
||||||
|
<el-table :data="tableData" style="width: 100%">
|
||||||
|
<el-table-column prop="date" label="Date" width="180" />
|
||||||
|
<el-table-column prop="name" label="Name" width="180" />
|
||||||
|
<el-table-column prop="address" label="Address" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页区域 -->
|
||||||
|
<div class="pagination-section">
|
||||||
|
<el-pagination
|
||||||
|
background
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
:total="1000"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-demo">
|
||||||
|
<!-- 柱状图示例 -->
|
||||||
|
<ECharts
|
||||||
|
:options="barChartOptions"
|
||||||
|
height="300px"
|
||||||
|
chartType="bar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import ECharts from '@/components/ECharts/index.vue'
|
||||||
|
|
||||||
|
const value = ref('')
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
value: 'Option1',
|
||||||
|
label: 'Option1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Option2',
|
||||||
|
label: 'Option2',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Option3',
|
||||||
|
label: 'Option3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Option4',
|
||||||
|
label: 'Option4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Option5',
|
||||||
|
label: 'Option5',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 树形数据
|
||||||
|
const treeData = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
label: '父级1',
|
||||||
|
children: [
|
||||||
|
{ id: 11, label: '子级1-1' },
|
||||||
|
{ id: 12, label: '子级1-2' },
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
label: '子级1-3',
|
||||||
|
children: [
|
||||||
|
{ id: 131, label: '子子级1-3-1' },
|
||||||
|
{ id: 132, label: '子子级1-3-2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
label: '父级2',
|
||||||
|
children: [
|
||||||
|
{ id: 21, label: '子级2-1' },
|
||||||
|
{ id: 22, label: '子级2-2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const tableData = [
|
||||||
|
{
|
||||||
|
date: '2016-05-03',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2016-05-02',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2016-05-04',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2016-05-01',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2016-05-03',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2016-05-02',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2016-05-04',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: '2016-05-01',
|
||||||
|
name: 'Tom',
|
||||||
|
address: 'No. 189, Grove St, Los Angeles',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
// 树形选择的配置属性
|
||||||
|
const treeProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'label',
|
||||||
|
children: 'children',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当前选中的值
|
||||||
|
const selectedValue = ref(null);
|
||||||
|
|
||||||
|
// 处理节点选中事件
|
||||||
|
const handleCheck = (currentNode, checkedNodes) => {
|
||||||
|
// 获取所有选中的节点
|
||||||
|
const checked = checkedNodes.checkedNodes;
|
||||||
|
|
||||||
|
// 用于存储最终的叶子节点id
|
||||||
|
const finalIds = new Set(); // 使用Set避免重复
|
||||||
|
|
||||||
|
checked.forEach(node => {
|
||||||
|
// 判断是否是叶子节点(即没有子节点)
|
||||||
|
if (!node.children || node.children.length === 0) {
|
||||||
|
finalIds.add(node.id);
|
||||||
|
} else {
|
||||||
|
// 如果是父级节点,递归获取其下的叶子节点id
|
||||||
|
const leafIds = getLeafIds(node, new Set());
|
||||||
|
leafIds.forEach(id => finalIds.add(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('最终的叶子节点id列表:', Array.from(finalIds));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 递归获取叶子节点id
|
||||||
|
const getLeafIds = (node, visited) => {
|
||||||
|
const ids = new Set();
|
||||||
|
if (node.children) {
|
||||||
|
node.children.forEach(child => {
|
||||||
|
if (!child.children || child.children.length === 0) {
|
||||||
|
// 如果是叶子节点,添加其id
|
||||||
|
ids.add(child.id);
|
||||||
|
} else {
|
||||||
|
// 如果不是叶子节点,继续递归
|
||||||
|
if (!visited.has(child.id)) {
|
||||||
|
visited.add(child.id);
|
||||||
|
const childIds = getLeafIds(child, visited);
|
||||||
|
childIds.forEach(id => ids.add(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义图表数据结构
|
||||||
|
const chartData = ref({
|
||||||
|
categories: ['手机', '电脑', '平板', '耳机', '手表'],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '上月销量',
|
||||||
|
type: 'bar',
|
||||||
|
data: [120, 200, 150, 80, 70],
|
||||||
|
color: '#409EFF',
|
||||||
|
showLabel: true,
|
||||||
|
labelFormatter: '{c}台'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '本月销量',
|
||||||
|
type: 'bar',
|
||||||
|
data: [140, 180, 180, 120, 90],
|
||||||
|
color: '#67C23A',
|
||||||
|
showLabel: true,
|
||||||
|
labelFormatter: '{c}台'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '环比增长',
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
data: [16.7, -10, 20, 50, 28.6],
|
||||||
|
color: '#E6A23C',
|
||||||
|
showLabel: true,
|
||||||
|
labelFormatter: '{c}%'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
name: '销量',
|
||||||
|
min: 0,
|
||||||
|
nameTextStyle: {
|
||||||
|
padding: [0, 0, 0, 50]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '增长率',
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
axisLabel: '{value}%',
|
||||||
|
nameTextStyle: {
|
||||||
|
padding: [0, 0, 0, 50]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 生成图表配置的函数
|
||||||
|
const generateChartOptions = (chartData) => {
|
||||||
|
const barSeries = chartData.series.filter(item => item.type === 'bar')
|
||||||
|
const lineSeries = chartData.series.filter(item => item.type === 'line')
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: {
|
||||||
|
text: '各类商品销量对比'
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross'
|
||||||
|
},
|
||||||
|
formatter: function(params) {
|
||||||
|
let result = `${params[0].axisValue}<br/>`;
|
||||||
|
// 计算同比增长
|
||||||
|
const currentValue = params.find(p => p.seriesName === '本月销量')?.value || 0;
|
||||||
|
const lastValue = params.find(p => p.seriesName === '上月销量')?.value || 0;
|
||||||
|
const growth = params.find(p => p.seriesName === '环比增长')?.value || 0;
|
||||||
|
|
||||||
|
// 添加所有系列的数据
|
||||||
|
params.forEach(param => {
|
||||||
|
const marker = param.marker;
|
||||||
|
const seriesName = param.seriesName;
|
||||||
|
const value = param.value;
|
||||||
|
const unit = param.seriesName.includes('增长') ? '%' : '台';
|
||||||
|
result += `${marker}${seriesName}: ${value}${unit}<br/>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加额外统计信息
|
||||||
|
if (currentValue && lastValue) {
|
||||||
|
const diff = currentValue - lastValue;
|
||||||
|
result += '<br/>';
|
||||||
|
result += `<span style="color: #666">销量差额: ${diff > 0 ? '+' : ''}${diff}台</span><br/>`;
|
||||||
|
result += `<span style="color: ${growth >= 0 ? '#67C23A' : '#F56C6C'}">环比: ${growth}%</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: chartData.series.map(item => item.name),
|
||||||
|
selected: chartData.series.reduce((acc, item) => {
|
||||||
|
acc[item.name] = true;
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: chartData.categories,
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
rotate: chartData.categories.length > 5 ? 30 : 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: chartData.yAxis.map((axis, index) => ({
|
||||||
|
type: 'value',
|
||||||
|
name: axis.name,
|
||||||
|
min: axis.min,
|
||||||
|
max: axis.max,
|
||||||
|
position: index === 0 ? 'left' : 'right',
|
||||||
|
splitLine: {
|
||||||
|
show: index === 0
|
||||||
|
},
|
||||||
|
axisLabel: axis.axisLabel ? {
|
||||||
|
formatter: axis.axisLabel
|
||||||
|
} : undefined
|
||||||
|
})),
|
||||||
|
series: chartData.series.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
type: item.type,
|
||||||
|
yAxisIndex: item.yAxisIndex || 0,
|
||||||
|
data: item.data,
|
||||||
|
barMaxWidth: 50, // 限制柱子最大宽度
|
||||||
|
barGap: '30%', // 柱子之间的间距
|
||||||
|
itemStyle: {
|
||||||
|
color: item.color,
|
||||||
|
borderRadius: item.type === 'bar' ? [4, 4, 0, 0] : 0
|
||||||
|
},
|
||||||
|
label: item.showLabel ? {
|
||||||
|
show: true,
|
||||||
|
position: item.type === 'line' ? 'top' : 'inside',
|
||||||
|
formatter: item.labelFormatter,
|
||||||
|
fontSize: 12,
|
||||||
|
color: item.type === 'line' ? item.color : '#fff'
|
||||||
|
} : undefined,
|
||||||
|
emphasis: {
|
||||||
|
focus: 'series',
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: item.type === 'line' ? 'top' : 'inside',
|
||||||
|
formatter: item.labelFormatter || '{c}',
|
||||||
|
fontSize: 12,
|
||||||
|
color: item.type === 'line' ? item.color : '#fff'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
grid: {
|
||||||
|
top: '15%',
|
||||||
|
bottom: '15%',
|
||||||
|
left: '10%',
|
||||||
|
right: '10%',
|
||||||
|
containLabel: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成图表配置
|
||||||
|
const barChartOptions = computed(() => generateChartOptions(chartData.value))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.test-center {
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-section {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.left-area {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-area {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-section {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
// 移除表格的外边框
|
||||||
|
--el-table-border: none;
|
||||||
|
// 减小表格行的上下padding
|
||||||
|
--el-table-row-height: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-section {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-demo {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -7,10 +7,9 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.announcement {
|
.announcement {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -204,7 +204,7 @@ const courses = ref([
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'C++面向对象编程精讲',
|
title: 'C++面向对象编程精讲',
|
||||||
description: '深入理解C++面向对象编程思想,掌握核心概念和实践技巧',
|
description: '深入理解C++面向对象编程思想,掌握核心概念和实践技巧',
|
||||||
coverImg: '/src/assets/images/course-covers/cpp.jpg',
|
coverImg: 'http://localhost:9000/file/c6d07740-7306-4fc1-b1f8-670684a73ed9/img/31c258e7-3fd4-402c-8f2f-c9455c6cfdaf.png',
|
||||||
teacher: '张教授',
|
teacher: '张教授',
|
||||||
studentCount: 1234,
|
studentCount: 1234,
|
||||||
rating: 4.8,
|
rating: 4.8,
|
||||||
|
|
|
@ -252,7 +252,7 @@ const courseData = ref({
|
||||||
docCount: 5,
|
docCount: 5,
|
||||||
totalDuration: '24小时',
|
totalDuration: '24小时',
|
||||||
enrolled: false,
|
enrolled: false,
|
||||||
coverImg: 'http://example.com/course-cover.jpg',
|
coverImg: 'http://localhost:9000/file/c6d07740-7306-4fc1-b1f8-670684a73ed9/img/31c258e7-3fd4-402c-8f2f-c9455c6cfdaf.png',
|
||||||
chapters: [
|
chapters: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div class="goods-detail">
|
||||||
|
<NavBar :active="'/shop'"></NavBar>
|
||||||
|
|
||||||
|
<el-card class="detail-card">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<img :src="goods.image" class="goods-image">
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<h1>{{ goods.name }}</h1>
|
||||||
|
<div class="goods-price">{{ goods.points }} 积分</div>
|
||||||
|
<div class="goods-desc">{{ goods.description }}</div>
|
||||||
|
<div class="goods-action">
|
||||||
|
<el-button type="primary" size="large" @click="handleExchange">
|
||||||
|
立即兑换
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,203 @@
|
||||||
|
<template>
|
||||||
|
<div class="shop-container">
|
||||||
|
<NavBar :active="'/shop'"></NavBar>
|
||||||
|
|
||||||
|
<!-- 积分余额显示 -->
|
||||||
|
<div class="points-balance">
|
||||||
|
<el-card>
|
||||||
|
<div class="balance-content">
|
||||||
|
<i class="el-icon-coin"></i>
|
||||||
|
<span class="points">当前积分: {{ userPoints }}</span>
|
||||||
|
<el-button type="primary" size="small" @click="showPointsHistory">
|
||||||
|
积分明细
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<div class="goods-list">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="6" v-for="item in goodsList" :key="item.id">
|
||||||
|
<el-card :body-style="{ padding: '0px' }" class="goods-card">
|
||||||
|
<img :src="item.image" class="goods-image">
|
||||||
|
<div class="goods-info">
|
||||||
|
<h3>{{ item.name }}</h3>
|
||||||
|
<div class="price-info">
|
||||||
|
<span class="points-price">{{ item.points }} 积分</span>
|
||||||
|
<el-button type="primary" size="small" @click="handleExchange(item)">
|
||||||
|
立即兑换
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 积分明细对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="积分明细"
|
||||||
|
v-model="pointsHistoryVisible"
|
||||||
|
width="60%">
|
||||||
|
<el-table :data="pointsHistory">
|
||||||
|
<el-table-column prop="time" label="时间" width="180"/>
|
||||||
|
<el-table-column prop="type" label="类型" width="120"/>
|
||||||
|
<el-table-column prop="points" label="积分变动"/>
|
||||||
|
<el-table-column prop="description" label="说明"/>
|
||||||
|
</el-table>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import NavBar from '@/components/oj/common/NavBar.vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Shop',
|
||||||
|
components: {
|
||||||
|
NavBar
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
userPoints: 1000, // 用户积分余额
|
||||||
|
pointsHistoryVisible: false,
|
||||||
|
goodsList: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '力扣会员1个月',
|
||||||
|
points: 500,
|
||||||
|
image: 'https://example.com/leetcode.png',
|
||||||
|
description: '力扣会员1个月使用权限'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '编程书籍优惠券',
|
||||||
|
points: 300,
|
||||||
|
image: 'https://example.com/book.png',
|
||||||
|
description: '任意编程书籍立减30元'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'IDE专业版',
|
||||||
|
points: 800,
|
||||||
|
image: 'https://example.com/ide.png',
|
||||||
|
description: 'JetBrains IDE 1个月使用授权'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: '在线课程优惠券',
|
||||||
|
points: 400,
|
||||||
|
image: 'https://example.com/course.png',
|
||||||
|
description: '指定在线课程优惠50元'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pointsHistory: [
|
||||||
|
{
|
||||||
|
time: '2024-03-20 10:00:00',
|
||||||
|
type: '题目通过',
|
||||||
|
points: '+10',
|
||||||
|
description: '完成题目【两数之和】'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
time: '2024-03-19 15:30:00',
|
||||||
|
type: '商品兑换',
|
||||||
|
points: '-300',
|
||||||
|
description: '兑换【编程书籍优惠券】'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
time: '2024-03-18 09:15:00',
|
||||||
|
type: '每日登录',
|
||||||
|
points: '+5',
|
||||||
|
description: '每日登录奖励'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showPointsHistory() {
|
||||||
|
this.pointsHistoryVisible = true
|
||||||
|
},
|
||||||
|
handleExchange(item) {
|
||||||
|
if (this.userPoints < item.points) {
|
||||||
|
ElMessage.warning('积分不足!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success('兑换成功!')
|
||||||
|
this.userPoints -= item.points
|
||||||
|
// TODO: 调用后端API完成兑换
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.shop-container {
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-balance {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-list {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-card {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-info {
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-info h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-info {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-price {
|
||||||
|
color: #F56C6C;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -139,7 +139,7 @@ export default {
|
||||||
// 可批量操作
|
// 可批量操作
|
||||||
multi: false,
|
multi: false,
|
||||||
// 列表请求URL
|
// 列表请求URL
|
||||||
listUrl: '/exam/api/exam/exam/online-paging'
|
listUrl: '/api/common/exam/api/exam/exam/online-paging'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue