diff --git a/api/upload.js b/api/upload.js
index e6ef622..c76e9e0 100644
--- a/api/upload.js
+++ b/api/upload.js
@@ -28,26 +28,26 @@ function getOssSignatureByPrefix(prefix, headerKey) {
});
}
-function registerOssFileByPrefix(prefix, headerKey, url) {
+function registerOssFileByPrefix(prefix, headerKey, url, fileSize = 0) {
return req.request({
url: `${prefix}register-oss-file`,
method: 'POST',
- data: { url, source: 2 },
+ data: { url, source: 2, file_size: fileSize },
header: {
[headerKey]: '1',
},
});
}
-function registerBusinessOssFile(url) {
+function registerBusinessOssFile(url, fileSize = 0) {
const mode = uni.getStorageSync('loginMode');
if (mode === 'platform_admin') {
- return registerOssFileByPrefix(PLATFORM_PREFIX, '_platformAdmin', url);
+ return registerOssFileByPrefix(PLATFORM_PREFIX, '_platformAdmin', url, fileSize);
}
if (mode === 'clinic_admin') {
- return registerOssFileByPrefix(CLINIC_ADMIN_PREFIX, '_clinicAdmin', url);
+ return registerOssFileByPrefix(CLINIC_ADMIN_PREFIX, '_clinicAdmin', url, fileSize);
}
- return registerOssFileByPrefix(SALESPERSON_PREFIX, '_salesperson', url);
+ return registerOssFileByPrefix(SALESPERSON_PREFIX, '_salesperson', url, fileSize);
}
export { registerBusinessOssFile };
diff --git a/common/js/oss-upload.js b/common/js/oss-upload.js
index 4460a4d..0a6d9f9 100644
--- a/common/js/oss-upload.js
+++ b/common/js/oss-upload.js
@@ -48,7 +48,7 @@ export async function getOssSignatureFromBackend() {
* @returns {Promise<{ url: string, objectName: string }>}
*/
export async function uploadToOss(options) {
- const { filePath, onProgress } = options || {};
+ const { filePath, onProgress, fileSize = 0 } = options || {};
if (!filePath) {
throw new Error('文件路径为空');
}
@@ -76,7 +76,7 @@ export async function uploadToOss(options) {
if (code >= 200 && code < 300 || code === 204) {
const host = String(signature.host).replace(/\/$/, '');
const url = `${host}/${objectName}`;
- registerBusinessOssFile(url).catch((err) => {
+ registerBusinessOssFile(url, fileSize).catch((err) => {
console.warn('OSS 文件登记失败:', err);
});
resolve({
diff --git a/subPackages/sub_salesperson/common/imageGalleryBridge.js b/subPackages/sub_salesperson/common/imageGalleryBridge.js
new file mode 100644
index 0000000..1308d75
--- /dev/null
+++ b/subPackages/sub_salesperson/common/imageGalleryBridge.js
@@ -0,0 +1,22 @@
+let pending = null
+
+export function requestGalleryPick() {
+ return new Promise((resolve, reject) => {
+ pending = { resolve, reject }
+ uni.$emit('salesperson:open-gallery')
+ })
+}
+
+export function resolveGalleryPick(url) {
+ if (pending) {
+ pending.resolve(url)
+ pending = null
+ }
+}
+
+export function cancelGalleryPick() {
+ if (pending) {
+ pending.reject(new Error('cancel'))
+ pending = null
+ }
+}
diff --git a/subPackages/sub_salesperson/common/inputFormHelpers.js b/subPackages/sub_salesperson/common/inputFormHelpers.js
index 6291dcc..6b3e3ea 100644
--- a/subPackages/sub_salesperson/common/inputFormHelpers.js
+++ b/subPackages/sub_salesperson/common/inputFormHelpers.js
@@ -1,45 +1,26 @@
import { prepareImagePath } from '@/common/js/image-compress.js'
import { uploadToOss } from '@/common/js/oss-upload.js'
+import { requestGalleryPick } from '@/subPackages/sub_salesperson/common/imageGalleryBridge.js'
-/**
- * 选择并上传图片(压缩 + OSS 直传)
- * @param {{ count?: number }} options count>1 时依次上传多张,返回 url 数组
- */
-export function pickAndUploadImage(options = {}) {
- const count = Math.min(Math.max(options.count || 1, 1), 9)
+function chooseImageFromDevice(count = 1) {
return new Promise((resolve, reject) => {
- uni.chooseImage({
- count,
- sizeType: ['original', 'compressed'],
- sourceType: ['album', 'camera'],
- success: async (res) => {
- uni.showLoading({ title: '上传中...', mask: true })
- try {
- const files = res.tempFiles || []
- const paths = files.length
- ? files.map((f) => f.path)
- : (res.tempFilePaths || [])
- const urls = []
- for (const path of paths) {
- const prepared = await prepareImagePath({
- path,
- size: files.find((f) => f.path === path)?.size,
- })
- if (!prepared) continue
- const result = await uploadToOss({ filePath: prepared.path })
- urls.push(result.url)
- }
- uni.hideLoading()
- if (!urls.length) {
- reject(new Error('上传失败'))
- return
- }
- resolve(count === 1 ? urls[0] : urls)
- } catch (e) {
- uni.hideLoading()
- uni.showToast({ title: e.message || '上传失败', icon: 'none' })
- reject(e)
- }
+ uni.showActionSheet({
+ itemList: ['拍照', '从相册选择'],
+ success: (sheet) => {
+ const sourceType = sheet.tapIndex === 0 ? ['camera'] : ['album']
+ uni.chooseImage({
+ count,
+ sizeType: ['original', 'compressed'],
+ sourceType,
+ success: (res) => resolve(res),
+ fail: (err) => {
+ if (err && err.errMsg && err.errMsg.indexOf('cancel') !== -1) {
+ reject(new Error('cancel'))
+ return
+ }
+ reject(err)
+ },
+ })
},
fail: (err) => {
if (err && err.errMsg && err.errMsg.indexOf('cancel') !== -1) {
@@ -52,6 +33,50 @@ export function pickAndUploadImage(options = {}) {
})
}
+async function uploadChosenImages(res, count = 1) {
+ uni.showLoading({ title: '上传中...', mask: true })
+ try {
+ const files = res.tempFiles || []
+ const paths = files.length ? files.map((f) => f.path) : (res.tempFilePaths || [])
+ const urls = []
+ for (const path of paths) {
+ const prepared = await prepareImagePath({
+ path,
+ size: files.find((f) => f.path === path)?.size,
+ })
+ if (!prepared) continue
+ const fileSize = files.find((f) => f.path === path)?.size || 0
+ const result = await uploadToOss({ filePath: prepared.path, fileSize })
+ urls.push(result.url)
+ }
+ uni.hideLoading()
+ if (!urls.length) throw new Error('上传失败')
+ return count === 1 ? urls[0] : urls
+ } catch (e) {
+ uni.hideLoading()
+ uni.showToast({ title: e.message || '上传失败', icon: 'none' })
+ throw e
+ }
+}
+
+/**
+ * 打开图库选择(由页面内 link 单独触发)
+ */
+export function openGalleryPick() {
+ return requestGalleryPick()
+}
+
+/**
+ * 选择并上传图片(压缩 + OSS 直传)
+ * @param {{ count?: number }} options count>1 时依次上传多张,返回 url 数组
+ */
+export function pickAndUploadImage(options = {}) {
+ const count = Math.min(Math.max(options.count || 1, 1), 9)
+ return chooseImageFromDevice(count)
+ .then((res) => uploadChosenImages(res, count))
+ .catch((err) => Promise.reject(err))
+}
+
function getUserDataPath() {
if (typeof wx !== 'undefined' && wx.env && wx.env.USER_DATA_PATH) {
return wx.env.USER_DATA_PATH
@@ -177,6 +202,7 @@ export function createDefaultDoctorInputForm() {
card_up: '',
card_down: '',
sign_image: '',
+ register_price: 0,
store_id: 0,
store_ids: [],
}
diff --git a/subPackages/sub_salesperson/components/ImageGalleryPicker.vue b/subPackages/sub_salesperson/components/ImageGalleryPicker.vue
new file mode 100644
index 0000000..2174697
--- /dev/null
+++ b/subPackages/sub_salesperson/components/ImageGalleryPicker.vue
@@ -0,0 +1,181 @@
+
+
+
+
+
+ 加载中...
+ 图库暂无图片
+
+
+
+ {{ item.file_size_text || '' }}
+ {{ item.created_at }}
+
+
+ 加载更多...
+
+
+
+
+
+
+
+
diff --git a/subPackages/sub_salesperson/components/business-page-layout.vue b/subPackages/sub_salesperson/components/business-page-layout.vue
index 3570eac..30b0a0c 100644
--- a/subPackages/sub_salesperson/components/business-page-layout.vue
+++ b/subPackages/sub_salesperson/components/business-page-layout.vue
@@ -10,6 +10,7 @@
+
import businessMixin from '@/subPackages/sub_salesperson/common/businessMixin.js';
+import ImageGalleryPicker from '@/subPackages/sub_salesperson/components/ImageGalleryPicker.vue';
export default {
name: 'BusinessPageLayout',
mixins: [businessMixin],
+ components: { ImageGalleryPicker },
props: {
title: { type: String, default: '' },
showBack: { type: Boolean, default: false },
diff --git a/subPackages/sub_salesperson/doctor-input/form.vue b/subPackages/sub_salesperson/doctor-input/form.vue
index 594d927..bea9984 100644
--- a/subPackages/sub_salesperson/doctor-input/form.vue
+++ b/subPackages/sub_salesperson/doctor-input/form.vue
@@ -28,6 +28,7 @@
上传头像
+ 从图库选择
@@ -96,6 +97,7 @@
上传签名图
+ 从图库选择
@@ -104,10 +106,14 @@
+
+
+
证照资料
+
*{{ item.label }}
@@ -117,6 +123,8 @@
上传
+ 从图库选择
+
@@ -128,6 +136,7 @@
关联门店{{ selectedStoreNames || '-' }}
科室/职称{{ selectedDepartLabel }} / {{ selectedTitleLabel }}
身份{{ selectedTypeLabel }}
+ 挂号费{{ form.register_price != null && form.register_price !== '' ? form.register_price : '0' }}
签名{{ form.sign_image ? '已填写' : '-' }}
@@ -164,6 +173,7 @@ import businessMixin from '@/subPackages/sub_salesperson/common/businessMixin.js
import { getInputBusinessContext } from '@/subPackages/sub_salesperson/common/context.js'
import {
pickAndUploadImage,
+ openGalleryPick,
uploadSignCanvasBase64,
uploadSignCanvasTempPath,
createDefaultDoctorInputForm,
@@ -347,6 +357,9 @@ export default {
uploadField(field) {
pickAndUploadImage().then((url) => { this.form[field] = url }).catch(() => {})
},
+ pickFromGallery(field) {
+ openGalleryPick().then((url) => { this.form[field] = url }).catch(() => {})
+ },
onSignCanvasSave(payload) {
this.showSignPad = false
const base64 = typeof payload === 'string' ? payload : (payload && payload.base64)
@@ -473,6 +486,7 @@ export default {
if (!this.validate()) return
const payload = {
...this.form,
+ register_price: Number(this.form.register_price) || 0,
store_input_ids: this.selectedStoreInputIds,
store_ids: this.selectedStoreIds,
store_id: this.selectedStoreInputIds[0] || this.selectedStoreIds[0] || 0,
@@ -544,6 +558,17 @@ export default {
gap: 12rpx;
}
.sign-tip { font-size: 26rpx; color: #6acdbb; }
+.upload-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 24rpx;
+ padding-bottom: 20rpx;
+}
+.upload-grid .upload-group {
+ margin-bottom: 0;
+ padding-bottom: 0;
+ border-bottom: none;
+}
.img-row, .img-list { display: flex; flex-wrap: wrap; gap: 24rpx; }
.thumb { width: 160rpx; height: 160rpx; border-radius: 12rpx; }
.upload-group { margin-bottom: 30rpx; padding-bottom: 20rpx; border-bottom: 1rpx solid #f5f5f5; }
@@ -561,6 +586,12 @@ export default {
border: 2rpx dashed #dcdfe6;
}
.upload-txt { font-size: 24rpx; color: #999; margin-top: 12rpx; }
+.gallery-link {
+ display: inline-block;
+ margin-top: 12rpx;
+ font-size: 24rpx;
+ color: #6acdbb;
+}
.summary-card { background: #f7f8fa; border-radius: 12rpx; padding: 24rpx; margin-bottom: 40rpx; }
.summary-row { display: flex; padding: 12rpx 0; font-size: 28rpx; }
.summary-row .k { width: 180rpx; color: #86909c; flex-shrink: 0; }
diff --git a/subPackages/sub_salesperson/store-input/form.vue b/subPackages/sub_salesperson/store-input/form.vue
index a046699..49aef64 100644
--- a/subPackages/sub_salesperson/store-input/form.vue
+++ b/subPackages/sub_salesperson/store-input/form.vue
@@ -103,6 +103,7 @@
上传公章
+ 从图库选择
@@ -113,6 +114,7 @@
+ 从图库选择
@@ -196,7 +198,7 @@ import RegionAddressPicker from '@/subPackages/sub_salesperson/components/Region
import PromoterPicker from '@/subPackages/sub_salesperson/components/PromoterPicker.vue'
import businessMixin from '@/subPackages/sub_salesperson/common/businessMixin.js'
import { getInputBusinessContext } from '@/subPackages/sub_salesperson/common/context.js'
-import { pickAndUploadImage, createDefaultStoreInputForm } from '@/subPackages/sub_salesperson/common/inputFormHelpers.js'
+import { pickAndUploadImage, openGalleryPick, createDefaultStoreInputForm } from '@/subPackages/sub_salesperson/common/inputFormHelpers.js'
import { getDraft, saveDraft, removeDraft } from '@/subPackages/sub_salesperson/common/inputDraftStorage.js'
import { serializeStoreDraft, applyStoreDraft } from '@/subPackages/sub_salesperson/common/inputDraftHelpers.js'
import { goDoctorInputFromStore } from '@/subPackages/sub_salesperson/common/inputNavigation.js'
@@ -360,6 +362,12 @@ export default {
uploadField(field) {
pickAndUploadImage().then(url => { this.form[field] = url }).catch(() => {})
},
+ pickFromGallery(field) {
+ openGalleryPick().then(url => { this.form[field] = url }).catch(() => {})
+ },
+ pickCarouselFromGallery() {
+ openGalleryPick().then(url => { this.form.url.push(url) }).catch(() => {})
+ },
uploadCarousel() {
pickAndUploadImage().then(url => { this.form.url.push(url) }).catch(() => {})
},
@@ -516,21 +524,21 @@ export default {
.time-box { flex: 1; text-align: center; background: #f5f7fa; padding: 12rpx 0; border-radius: 8rpx; font-size: 28rpx; color: #606266; }
.time-sep { margin: 0 20rpx; color: #999; }
-.clinic-type-cards { display: flex; gap: 20rpx; width: 100%; }
+.clinic-type-cards { display: flex; gap: 12rpx; width: 100%; }
.clinic-type-card {
flex: 1;
- padding: 24rpx 16rpx;
+ padding: 12rpx 10rpx;
text-align: center;
background: #f5f7fa;
border: 2rpx solid #e4e7ed;
- border-radius: 12rpx;
+ border-radius: 8rpx;
box-sizing: border-box;
}
.clinic-type-card.active {
background: rgba(106, 205, 187, 0.08);
border-color: #6acdbb;
}
-.clinic-type-label { font-size: 28rpx; color: #303133; }
+.clinic-type-label { font-size: 24rpx; color: #303133; }
.clinic-type-card.active .clinic-type-label { color: #6acdbb; font-weight: 600; }
.switch-row {
display: flex;
@@ -550,6 +558,12 @@ export default {
.thumb { width: 160rpx; height: 160rpx; border-radius: 12rpx; box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05); }
.upload-btn { width: 160rpx; height: 160rpx; background: #f4f5f7; border-radius: 12rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 2rpx dashed #dcdfe6; }
.upload-txt { font-size: 24rpx; color: #999; margin-top: 12rpx; }
+.gallery-link {
+ display: inline-block;
+ margin-top: 12rpx;
+ font-size: 24rpx;
+ color: #6acdbb;
+}
.contract-btn { width: 100%; height: 100rpx; flex-direction: row; gap: 12rpx; background: rgba(106, 205, 187, 0.05); border-color: #6acdbb; }
/* 底部操作区 */