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:
@@ -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
@@ -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. **跨域问题**
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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' } : {}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -13,7 +13,7 @@ export default defineConfig({
|
||||
port: 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8002',
|
||||
target: 'http://localhost:8083',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user