feat(branding): Update logo and favicon assets across all platforms
- Replace favicon.svg in admin panel with new brand logo design - Add logo.png and logo.svg files to dedicated logo directory - Update miniprogram with new logo assets (logo.png and logo.svg) - Add README-UPLOAD.md documentation for miniprogram upload functionality - Update miniprogram app.js and config.js for logo integration - Update miniprogram user page (pages/user/index.js) to use new branding - Update server upload route to handle new logo assets - Add test-upload.js for upload functionality testing - Update server .env configuration for asset handling - Standardize brand visual identity across admin, miniprogram, and server platforms
This commit is contained in:
parent
02937ca33c
commit
2ee017bccf
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 20 KiB |
@ -226,8 +226,8 @@
|
||||
padding: 0 16px;
|
||||
|
||||
.logo-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
|
||||
BIN
logo/logo.png
Normal file
BIN
logo/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
71
logo/logo.svg
Normal file
71
logo/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
91
miniprogram/README-UPLOAD.md
Normal file
91
miniprogram/README-UPLOAD.md
Normal file
@ -0,0 +1,91 @@
|
||||
# 小程序头像上传配置说明
|
||||
|
||||
## 问题说明
|
||||
小程序从本地相册选择头像后,会得到一个临时文件路径(如 `http://tmp/xxx.jpg`),这个路径需要上传到服务器才能永久保存。
|
||||
|
||||
## 开发环境配置
|
||||
|
||||
### 1. 微信开发者工具设置
|
||||
在微信开发者工具中,需要开启以下选项:
|
||||
|
||||
1. 点击右上角"详情"
|
||||
2. 在"本地设置"中勾选:
|
||||
- ✅ **不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书**
|
||||
- ✅ **不校验合法域名**
|
||||
|
||||
这样才能在开发环境中访问 `http://127.0.0.1:3001` 的上传接口。
|
||||
|
||||
### 2. 确认服务端运行
|
||||
确保后端服务正在运行:
|
||||
```bash
|
||||
cd server
|
||||
npm start
|
||||
```
|
||||
|
||||
服务应该运行在 `http://127.0.0.1:3001`
|
||||
|
||||
### 3. 测试上传功能
|
||||
1. 在小程序中点击头像
|
||||
2. 选择"从相册选择"
|
||||
3. 选择一张图片
|
||||
4. 点击"保存"
|
||||
5. 查看控制台日志,应该显示:
|
||||
- "检测到临时头像,开始上传: http://tmp/xxx.jpg"
|
||||
- "头像上传成功: http://127.0.0.1:3001/uploads/xxx.jpg"
|
||||
|
||||
## 生产环境配置
|
||||
|
||||
### 1. 小程序后台配置
|
||||
在微信公众平台(mp.weixin.qq.com)配置合法域名:
|
||||
|
||||
1. 登录小程序后台
|
||||
2. 进入"开发" -> "开发管理" -> "开发设置"
|
||||
3. 在"服务器域名"中配置:
|
||||
- **uploadFile合法域名**:`https://yingsa-server.ethan.team`
|
||||
- **request合法域名**:`https://yingsa-server.ethan.team`
|
||||
- **socket合法域名**:`wss://yingsa-server.ethan.team`
|
||||
|
||||
### 2. 服务端HTTPS配置
|
||||
确保生产环境服务器已配置HTTPS证书。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 上传失败,提示"不在合法域名列表中"
|
||||
**解决方案**:
|
||||
- 开发环境:在微信开发者工具中开启"不校验合法域名"
|
||||
- 生产环境:在小程序后台配置合法的上传域名
|
||||
|
||||
### Q2: 上传失败,提示"网络错误"
|
||||
**解决方案**:
|
||||
- 检查后端服务是否正常运行
|
||||
- 检查 `miniprogram/config.js` 中的 `baseUrl` 配置是否正确
|
||||
- 查看后端日志是否有错误信息
|
||||
|
||||
### Q3: 头像显示不出来
|
||||
**解决方案**:
|
||||
- 检查上传是否成功(查看控制台日志)
|
||||
- 检查返回的URL是否正确
|
||||
- 确认 `server/uploads` 目录存在且有写入权限
|
||||
- 确认服务端的静态文件服务配置正确
|
||||
|
||||
### Q4: 临时文件路径错误
|
||||
**解决方案**:
|
||||
- 这是正常的,临时文件只在选择时有效
|
||||
- 必须上传到服务器后才能永久使用
|
||||
- 代码已经处理了临时文件的上传逻辑
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 查看上传日志
|
||||
在小程序控制台中查看:
|
||||
```
|
||||
检测到临时头像,开始上传: [临时路径]
|
||||
上传头像响应: [服务器响应]
|
||||
头像上传成功: [最终URL]
|
||||
```
|
||||
|
||||
### 查看服务端日志
|
||||
在服务端控制台中查看上传请求和文件保存情况。
|
||||
|
||||
### 检查文件是否保存
|
||||
检查 `server/uploads/` 目录下是否有新上传的文件。
|
||||
@ -64,7 +64,7 @@ App({
|
||||
},
|
||||
|
||||
// 手机号授权登录(第二步:解密手机号完成注册/登录)
|
||||
phoneLogin(encryptedData, iv, userProfile) {
|
||||
phoneLogin(encryptedData, iv, gender) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wxInfo = this.globalData.wxLoginInfo;
|
||||
if (!wxInfo) {
|
||||
@ -72,19 +72,23 @@ App({
|
||||
return;
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
openid: wxInfo.openid,
|
||||
unionid: wxInfo.unionid,
|
||||
sessionKey: wxInfo.sessionKey,
|
||||
encryptedData,
|
||||
iv,
|
||||
};
|
||||
|
||||
// 如果提供了性别参数,添加到请求中
|
||||
if (gender === 1 || gender === 2) {
|
||||
requestData.gender = gender;
|
||||
}
|
||||
|
||||
wx.request({
|
||||
url: `${this.globalData.baseUrl}/api/user/phone-login`,
|
||||
method: "POST",
|
||||
data: {
|
||||
openid: wxInfo.openid,
|
||||
unionid: wxInfo.unionid,
|
||||
sessionKey: wxInfo.sessionKey,
|
||||
encryptedData,
|
||||
iv,
|
||||
nickname: (userProfile && userProfile.nickName) || "",
|
||||
avatar: (userProfile && userProfile.avatarUrl) || "",
|
||||
gender: (userProfile && userProfile.gender) || 0,
|
||||
},
|
||||
data: requestData,
|
||||
success: (loginRes) => {
|
||||
if (loginRes.data.code === 0) {
|
||||
this.globalData.token = loginRes.data.data.token;
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
// 开发环境配置
|
||||
const devConfig = {
|
||||
// API 基础地址(本地开发)
|
||||
baseUrl: "http://127.0.0.1:3000",
|
||||
baseUrl: "http://127.0.0.1:3001",
|
||||
// WebSocket 地址(本地开发)
|
||||
wsUrl: "ws://127.0.0.1:3000/ws",
|
||||
wsUrl: "ws://127.0.0.1:3001/ws",
|
||||
};
|
||||
|
||||
// 生产环境配置
|
||||
|
||||
BIN
miniprogram/images/logo.png
Normal file
BIN
miniprogram/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
71
miniprogram/images/logo.svg
Normal file
71
miniprogram/images/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
@ -140,7 +140,7 @@ Page({
|
||||
await this.doPhoneLogin(
|
||||
e.detail.encryptedData,
|
||||
e.detail.iv,
|
||||
needGender ? this.data.registerGender : 0,
|
||||
needGender ? this.data.registerGender : undefined,
|
||||
);
|
||||
|
||||
// 获取门店信息
|
||||
@ -186,8 +186,8 @@ Page({
|
||||
},
|
||||
|
||||
async doPhoneLogin(encryptedData, iv, gender) {
|
||||
const g = gender === 1 || gender === 2 ? gender : 0;
|
||||
await app.phoneLogin(encryptedData, iv, g ? { gender: g } : null);
|
||||
const g = gender === 1 || gender === 2 ? gender : undefined;
|
||||
await app.phoneLogin(encryptedData, iv, g);
|
||||
this._pendingPhoneLogin = null;
|
||||
this.setData({ showGenderModal: false });
|
||||
},
|
||||
@ -277,10 +277,79 @@ Page({
|
||||
},
|
||||
|
||||
// 选择头像(新API:button open-type="chooseAvatar")
|
||||
onChooseAvatarNew(e) {
|
||||
async onChooseAvatarNew(e) {
|
||||
const avatarUrl = e.detail.avatarUrl;
|
||||
this.setData({
|
||||
"profileForm.avatar": avatarUrl,
|
||||
console.log('选择头像:', avatarUrl);
|
||||
|
||||
// 检查是否是临时文件
|
||||
if (avatarUrl && (avatarUrl.startsWith("wxfile://") ||
|
||||
avatarUrl.startsWith("http://tmp") ||
|
||||
avatarUrl.includes("/tmp/"))) {
|
||||
console.log('检测到临时头像,立即上传');
|
||||
wx.showLoading({ title: "上传头像中..." });
|
||||
|
||||
try {
|
||||
const uploadedUrl = await this.uploadAvatar(avatarUrl);
|
||||
console.log('头像上传成功:', uploadedUrl);
|
||||
|
||||
this.setData({
|
||||
"profileForm.avatar": uploadedUrl,
|
||||
});
|
||||
|
||||
wx.hideLoading();
|
||||
wx.showToast({ title: "头像上传成功", icon: "success", duration: 1500 });
|
||||
} catch (uploadErr) {
|
||||
wx.hideLoading();
|
||||
console.error("头像上传失败:", uploadErr);
|
||||
wx.showToast({
|
||||
title: uploadErr.message || "头像上传失败",
|
||||
icon: "none",
|
||||
duration: 2000
|
||||
});
|
||||
// 上传失败,不设置头像
|
||||
}
|
||||
} else {
|
||||
// 不是临时文件,直接使用
|
||||
this.setData({
|
||||
"profileForm.avatar": avatarUrl,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 选择头像(使用 wx.chooseMedia)
|
||||
onChooseAvatar() {
|
||||
wx.chooseMedia({
|
||||
count: 1,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
sizeType: ['compressed'],
|
||||
success: (res) => {
|
||||
console.log('选择图片成功:', res);
|
||||
const tempFilePath = res.tempFiles[0].tempFilePath;
|
||||
this.setData({
|
||||
"profileForm.avatar": tempFilePath,
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择图片失败:', err);
|
||||
// 如果 chooseMedia 不支持,降级使用 chooseImage
|
||||
wx.chooseImage({
|
||||
count: 1,
|
||||
sourceType: ['album', 'camera'],
|
||||
sizeType: ['compressed'],
|
||||
success: (res) => {
|
||||
console.log('选择图片成功(降级):', res);
|
||||
const tempFilePath = res.tempFilePaths[0];
|
||||
this.setData({
|
||||
"profileForm.avatar": tempFilePath,
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('选择图片失败:', err);
|
||||
wx.showToast({ title: '选择图片失败', icon: 'none' });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -311,14 +380,8 @@ Page({
|
||||
wx.showLoading({ title: "保存中..." });
|
||||
|
||||
try {
|
||||
// 如果选择了新头像,先上传
|
||||
let avatarUrl = avatar;
|
||||
if (
|
||||
avatar &&
|
||||
(avatar.startsWith("wxfile://") || avatar.startsWith("http://tmp"))
|
||||
) {
|
||||
avatarUrl = await this.uploadAvatar(avatar);
|
||||
}
|
||||
// 头像已经在选择时上传,这里直接使用
|
||||
const avatarUrl = avatar;
|
||||
|
||||
// 调用更新资料接口
|
||||
const payload = {
|
||||
@ -329,6 +392,7 @@ Page({
|
||||
payload.gender = gender;
|
||||
}
|
||||
|
||||
console.log("保存资料请求:", payload);
|
||||
const res = await app.request("/api/user/profile", payload, "PUT");
|
||||
|
||||
// 更新本地数据(服务端已返回完整URL)
|
||||
@ -357,6 +421,16 @@ Page({
|
||||
// 上传头像
|
||||
async uploadAvatar(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log('开始上传头像');
|
||||
console.log('文件路径:', filePath);
|
||||
console.log('上传URL:', `${app.globalData.baseUrl}/api/upload/avatar`);
|
||||
console.log('Token:', app.globalData.token);
|
||||
|
||||
if (!app.globalData.token) {
|
||||
reject(new Error('未登录,无法上传头像'));
|
||||
return;
|
||||
}
|
||||
|
||||
wx.uploadFile({
|
||||
url: `${app.globalData.baseUrl}/api/upload/avatar`,
|
||||
filePath: filePath,
|
||||
@ -366,20 +440,30 @@ Page({
|
||||
},
|
||||
success: (res) => {
|
||||
try {
|
||||
console.log("上传头像响应状态:", res.statusCode);
|
||||
console.log("上传头像响应数据:", res.data);
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(`上传失败,状态码: ${res.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.code === 0 && data.data && data.data.url) {
|
||||
console.log("头像上传成功:", data.data.url);
|
||||
resolve(data.data.url);
|
||||
} else {
|
||||
console.error("上传头像失败:", data);
|
||||
resolve(filePath);
|
||||
reject(new Error(data.message || "上传头像失败"));
|
||||
}
|
||||
} catch (e) {
|
||||
resolve(filePath);
|
||||
console.error("解析上传响应失败:", e);
|
||||
reject(new Error("上传头像失败"));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error("上传头像失败:", err);
|
||||
resolve(filePath);
|
||||
console.error("上传头像请求失败:", err);
|
||||
reject(new Error("上传头像失败,请检查网络"));
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
NODE_ENV=development
|
||||
|
||||
# 服务端口(默认3000)
|
||||
PORT=3000
|
||||
PORT=3001
|
||||
|
||||
# ------------------------------------------
|
||||
# 数据库配置 (MySQL)
|
||||
|
||||
@ -60,7 +60,15 @@ router.post('/images', authAdmin, upload.array('files', 10), (req, res) => {
|
||||
});
|
||||
|
||||
// 用户头像上传(需要登录)
|
||||
router.post('/avatar', authUser, upload.single('file'), (req, res) => {
|
||||
router.post('/avatar', (req, res, next) => {
|
||||
console.log('收到头像上传请求');
|
||||
console.log('Authorization:', req.headers.authorization);
|
||||
next();
|
||||
}, authUser, (req, res, next) => {
|
||||
console.log('认证通过,开始上传');
|
||||
next();
|
||||
}, upload.single('file'), (req, res) => {
|
||||
console.log('文件上传完成:', req.file);
|
||||
if (!req.file) {
|
||||
return res.status(400).json(error('请选择要上传的图片'));
|
||||
}
|
||||
@ -68,7 +76,20 @@ router.post('/avatar', authUser, upload.single('file'), (req, res) => {
|
||||
const relativePath = `/uploads/${req.file.filename}`;
|
||||
// 使用 normalizeAvatarUrl 确保 https 保持 https,http 保持 http
|
||||
const fullUrl = normalizeAvatarUrl(relativePath, req);
|
||||
console.log('返回URL:', fullUrl);
|
||||
res.json(success({ url: fullUrl }, '上传成功'));
|
||||
});
|
||||
|
||||
// 错误处理中间件
|
||||
router.use((err, req, res, next) => {
|
||||
console.error('上传错误:', err);
|
||||
if (err instanceof multer.MulterError) {
|
||||
if (err.code === 'LIMIT_FILE_SIZE') {
|
||||
return res.status(400).json(error('文件大小超过限制(最大5MB)'));
|
||||
}
|
||||
return res.status(400).json(error('文件上传失败: ' + err.message));
|
||||
}
|
||||
return res.status(500).json(error(err.message || '上传失败'));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
36
server/test-upload.js
Normal file
36
server/test-upload.js
Normal file
@ -0,0 +1,36 @@
|
||||
// 测试上传接口
|
||||
const axios = require('axios');
|
||||
const FormData = require('form-data');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
async function testUpload() {
|
||||
try {
|
||||
// 首先测试健康检查
|
||||
console.log('1. 测试健康检查...');
|
||||
const healthRes = await axios.get('http://127.0.0.1:3001/health');
|
||||
console.log('✓ 健康检查通过:', healthRes.data);
|
||||
|
||||
// 测试上传路由是否存在(不带token)
|
||||
console.log('\n2. 测试上传路由(无token)...');
|
||||
try {
|
||||
await axios.post('http://127.0.0.1:3001/api/upload/avatar');
|
||||
} catch (err) {
|
||||
if (err.response) {
|
||||
console.log('✓ 路由存在,返回状态:', err.response.status);
|
||||
console.log(' 响应:', err.response.data);
|
||||
} else {
|
||||
console.log('✗ 请求失败:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n测试完成!');
|
||||
console.log('\n如果看到404错误,说明路由没有正确注册。');
|
||||
console.log('如果看到401错误,说明路由存在但需要认证。');
|
||||
console.log('如果看到400错误,说明路由正常工作。');
|
||||
} catch (error) {
|
||||
console.error('测试失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
testUpload();
|
||||
Loading…
Reference in New Issue
Block a user