Add initial files for Audio Dashboard Management System

- Created .env.example for environment variable configuration.
- Added docker-compose.yml for service orchestration.
- Implemented frequency response extraction in extract_frequency_response.py and convert_to_frequency_db.py.
- Generated output files: frequency_response_detailed.json, frequency_response_points.json, frequency_response.csv, and frequency_response_curve.png.
- Included sample measurement data for FiiO FA19 in CSV format.
This commit is contained in:
yangy
2026-03-17 14:20:08 +08:00
parent 75b1d16453
commit 90aec37461
25 changed files with 7570 additions and 47 deletions
+31
View File
@@ -0,0 +1,31 @@
FROM node:18-alpine AS builder
WORKDIR /app
# Install pnpm
RUN npm install -g pnpm
# Copy package files
COPY package.json pnpm-lock.yaml* ./
# Install dependencies
RUN pnpm install --frozen-lockfile || pnpm install
# Copy source code
COPY . .
# Build the application
RUN pnpm build
# Production stage
FROM nginx:alpine
# Copy built assets from builder stage
COPY --from=builder /app/dist /usr/share/nginx/html
# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+3 -3
View File
@@ -92,19 +92,19 @@ pnpm build
开发服务器已配置代理,将 `/api` 请求转发到后端服务:
- 前端请求:`/api/brands/`
- 后端地址:`http://localhost:8002/api/brands/`
- 后端地址:`http://localhost:8083/api/brands/`
配置位于 `vite.config.js`
## 注意事项
1. **确保后端服务已启动**
- 后端运行在 http://localhost:8002
- 后端运行在 http://localhost:8083
- 使用 `backend/restart.bat` 重启后端服务
2. **端口占用**
- 前端使用 3000 端口
- 后端使用 8002 端口
- 后端使用 8083 端口
- 如果端口被占用,请修改相应配置文件
3. **跨域问题**
+30
View File
@@ -0,0 +1,30 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# API proxy to backend
location /api {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Handle Vue Router history mode
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
+2 -1
View File
@@ -11,7 +11,8 @@
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"axios": "^1.5.0",
"element-plus": "^2.3.14"
"element-plus": "^2.3.14",
"@element-plus/icons-vue": "^2.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
+13 -8
View File
@@ -29,26 +29,31 @@ export function getModel(id) {
/**
* 创建型号
* @param {Object} data - 型号数据
* @param {Object} data - 型号数据或 FormData
* @param {Object} config - 额外配置
* @param {boolean} config.isFormData - 是否为 FormData 上传
*/
export function createModel(data) {
export function createModel(data, config = {}) {
return request({
url: '/models/',
method: 'post',
data
data,
headers: config.isFormData ? { 'Content-Type': 'multipart/form-data' } : {}
})
}
/**
* 更新型号
* @param {Object} data - 型号数据
* @param {number} data.id - 型号 ID
* @param {Object} data - 型号数据或 FormData
* @param {Object} config - 额外配置
* @param {boolean} config.isFormData - 是否为 FormData 上传
*/
export function updateModel(data) {
export function updateModel(data, config = {}) {
return request({
url: `/models/${data.id}`,
url: `/models/${data.id || data.get('id')}`,
method: 'put',
data
data,
headers: config.isFormData ? { 'Content-Type': 'multipart/form-data' } : {}
})
}
+77 -16
View File
@@ -80,7 +80,7 @@
</template>
</el-table-column>
<el-table-column prop="rig" label="阻抗" width="120" />
<el-table-column prop="eq_key" label="EQ 配置" width="150" show-overflow-tooltip />
<el-table-column prop="source" label="来源" width="150" show-overflow-tooltip />
<el-table-column prop="create_at" label="创建时间" width="180" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
@@ -177,12 +177,27 @@
maxlength="100"
/>
</el-form-item>
<el-form-item label="EQ 键" prop="eq_key">
<el-input
v-model="formData.eq_key"
placeholder="EQ 配置键值"
maxlength="255"
/>
<el-form-item label="频响文件" prop="measurement_file">
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
:file-list="fileList"
:limit="1"
accept=".csv,.txt,.json"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
拖拽文件到此处或<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持 csv/txt/json 格式保留原文件名
</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
@@ -198,7 +213,7 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Refresh, Plus, Upload } from '@element-plus/icons-vue'
import { Search, Refresh, Plus, Upload, UploadFilled } from '@element-plus/icons-vue'
import { getModels, createModel, updateModel, deleteModel, pushToMeilisearch } from '@/api/model'
import { getBrands } from '@/api/brand'
@@ -208,8 +223,11 @@ const pushLoading = ref(false)
const dialogVisible = ref(false)
const dialogTitle = ref('新型号')
const formRef = ref(null)
const uploadRef = ref(null)
const brandOptions = ref([])
const selectedIds = ref([])
const fileList = ref([])
const uploadedFile = ref(null)
const searchForm = reactive({
brand_name: '',
@@ -244,6 +262,20 @@ const formRules = {
]
}
// 处理文件变化
const handleFileChange = (file) => {
uploadedFile.value = file.raw
fileList.value = [file]
// 自动填写来源为 Eafonyoung
formData.source = 'Eafonyoung'
}
// 处理文件移除
const handleFileRemove = () => {
uploadedFile.value = null
fileList.value = []
}
// 加载品牌列表
const loadBrands = async () => {
try {
@@ -353,15 +385,39 @@ const handleSubmit = async () => {
if (valid) {
submitLoading.value = true
try {
const api = formData.id ? updateModel : createModel
const res = await api(formData)
if (res.code === 1) {
ElMessage.success(formData.id ? '更新成功' : '创建成功')
dialogVisible.value = false
loadData()
// 如果有文件,使用 FormData 上传
if (uploadedFile.value) {
const formDataUpload = new FormData()
formDataUpload.append('brand_name', formData.brand_name)
formDataUpload.append('name', formData.name)
formDataUpload.append('form', formData.form)
formDataUpload.append('rig', formData.rig)
formDataUpload.append('source', formData.source)
formDataUpload.append('eq_key', formData.eq_key || '')
formDataUpload.append('measurement_file', uploadedFile.value)
const api = formData.id ? updateModel : createModel
const res = await api(formDataUpload, { isFormData: true })
if (res.code === 1) {
ElMessage.success(formData.id ? '更新成功' : '创建成功')
dialogVisible.value = false
loadData()
} else {
ElMessage.error(res.msg || '操作失败')
}
} else {
ElMessage.error(res.msg || '操作失败')
// 没有文件,使用普通 JSON 提交
const api = formData.id ? updateModel : createModel
const res = await api(formData)
if (res.code === 1) {
ElMessage.success(formData.id ? '更新成功' : '创建成功')
dialogVisible.value = false
loadData()
} else {
ElMessage.error(res.msg || '操作失败')
}
}
} catch (error) {
console.error('操作失败:', error)
@@ -382,9 +438,14 @@ const handleDialogClose = () => {
formData.rig = ''
formData.source = ''
formData.eq_key = ''
uploadedFile.value = null
fileList.value = []
if (formRef.value) {
formRef.value.resetFields()
}
if (uploadRef.value) {
uploadRef.value.clearFiles()
}
}
// 分页变化
+1 -1
View File
@@ -10,7 +10,7 @@ cd /d "%~dp0"
echo 启动开发服务器...
echo.
echo 访问地址:http://localhost:3000
echo API 代理:http://localhost:8002
echo API 代理:http://localhost:8083
echo.
pnpm dev
+1 -1
View File
@@ -13,7 +13,7 @@ export default defineConfig({
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8002',
target: 'http://localhost:8083',
changeOrigin: true
}
}