feat: add mini-program privacy auth, update oss urls and refine photo guide

update oss static resource urls to oss.nianxx.cn
refactor privacy component to be reusable, add wechat mini-program privacy authorization check before image picking
add image urls to photo guide examples, refine photo guide ui styles and remove unused avatarSrc prop
This commit is contained in:
duanshuwen
2026-07-13 20:14:49 +08:00
parent 29bf6afe55
commit b5952961fe
6 changed files with 123 additions and 150 deletions

View File

@@ -1,12 +1,12 @@
<template> <template>
<view class="privacy" v-if="showPrivacy"> <view v-if="visible" class="privacy">
<view class="content"> <view class="content">
<view class="title">隐私保护指引</view> <view class="title">隐私保护指引</view>
<view class="des"> <view class="des">
请您仔细阅读并充分理解<text 请您仔细阅读并充分理解<text
class="link" class="link"
@click="handleOpenPrivacyContract" @click="handleOpenPrivacyContract"
>{{ privacyContractName }}</text >{{ contractName }}</text
> >
如您同意前述协议的全部内容请点击同意开始使用<text class="cancel" 如您同意前述协议的全部内容请点击同意开始使用<text class="cancel"
>如您不同意将被限制使用部分功能或将在您使用具体功能前再次询问以取得您的授权同意</text >如您不同意将被限制使用部分功能或将在您使用具体功能前再次询问以取得您的授权同意</text
@@ -28,46 +28,27 @@
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; defineProps({
import { onShow } from "@dcloudio/uni-app"; visible: {
type: Boolean,
const showPrivacy = ref(false); default: false,
const privacyContractName = ref("隐私保护指引"); },
contractName: {
onShow(() => { type: String,
// 条件编译微信小程序 default: "隐私保护指引",
// #ifdef MP-WEIXIN },
wx.getPrivacySetting({
success: (res) => {
console.log("cj隐私配置", res);
if (res.errMsg == "getPrivacySetting:ok") {
privacyContractName.value = res.privacyContractName;
showPrivacy.value = !!res.needAuthorization;
}
},
});
// #endif
// 条件编译抖音小程序
// #ifdef MP-TOUTIAO
uni.getPrivacySetting({
success: (res) => {
console.log(res);
showPrivacy.value = !!res.needAuthorization;
},
});
// #endif
}); });
const emit = defineEmits(["agree", "disagree"]);
// 拒绝 // 拒绝
const handleDisagree = () => { const handleDisagree = () => {
showPrivacy.value = false; emit("disagree");
}; };
// 同意 // 同意
const handleAgreePrivacyAuthorization = () => { const handleAgreePrivacyAuthorization = () => {
showPrivacy.value = false; emit("agree");
}; };
// 打开隐私保护指引 // 打开隐私保护指引

View File

@@ -1,56 +1,42 @@
<template> <template>
<uni-popup <uni-popup ref="popupRef" type="bottom" background-color="transparent" mask-background-color="rgba(0, 0, 0, 0)"
ref="popupRef" :safe-area="false" :is-mask-click="false">
type="bottom"
background-color="transparent"
mask-background-color="rgba(0, 0, 0, 0)"
:safe-area="false"
:is-mask-click="false"
>
<view class="photo-guide-panel"> <view class="photo-guide-panel">
<view class="photo-guide-close" @tap="emit('close')"> <view class="photo-guide-close" @tap="emit('close')">
<uni-icons type="closeempty" size="30" color="#8fa2ba" /> <uni-icons type="closeempty" size="30" color="#8fa2ba" />
</view> </view>
<text class="photo-guide-title">选图说明</text> <text class="photo-guide-title">选图说明</text>
<view class="photo-guide-section"> <view class="photo-guide-section">
<text class="photo-guide-section-title">正确照片示例</text> <text class="photo-guide-section-title">正确照片示例</text>
<view class="photo-guide-grid"> <view class="photo-guide-grid">
<view <view v-for="item in positiveExamples" :key="item.id" class="photo-example-card is-positive"
v-for="item in positiveExamples" :class="`example-${item.variant}`">
:key="item.id" <view class="photo-example-thumb">
class="photo-example-card is-positive" <image class="photo-example-image" :src="item.imageUrl" mode="aspectFill" />
:class="`example-${item.variant}`" </view>
> <text class="photo-example-caption">{{ item.label }}</text>
<view class="photo-example-thumb">
<image class="photo-example-image" :src="avatarSrc" mode="aspectFill" />
</view> </view>
<text class="photo-example-caption">{{ item.label }}</text>
</view> </view>
</view> </view>
</view>
<view class="photo-guide-section"> <view class="photo-guide-section">
<text class="photo-guide-section-title">错误照片示例</text> <text class="photo-guide-section-title">错误照片示例</text>
<view class="photo-guide-grid"> <view class="photo-guide-grid">
<view <view v-for="item in negativeExamples" :key="item.id" class="photo-example-card is-negative"
v-for="item in negativeExamples" :class="`example-${item.variant}`">
:key="item.id" <view class="photo-example-thumb">
class="photo-example-card is-negative" <image class="photo-example-image" :src="item.imageUrl" mode="aspectFill" />
:class="`example-${item.variant}`" </view>
> <text class="photo-example-caption">{{ item.label }}</text>
<view class="photo-example-thumb">
<image class="photo-example-image" :src="avatarSrc" mode="aspectFill" />
</view> </view>
<text class="photo-example-caption">{{ item.label }}</text>
</view> </view>
</view> </view>
</view>
<view class="photo-guide-primary" @tap="emit('confirm')"> <view class="photo-guide-primary" @tap="emit('confirm')">
我已了解开始选图 我已了解开始选图
</view> </view>
</view> </view>
</uni-popup> </uni-popup>
</template> </template>
@@ -59,10 +45,6 @@
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, ref } from "vue";
defineProps({ defineProps({
avatarSrc: {
type: String,
default: "",
},
positiveExamples: { positiveExamples: {
type: Array, type: Array,
default: () => [], default: () => [],

View File

@@ -50,7 +50,7 @@
.photo-guide-grid { .photo-guide-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px; gap: 12px;
} }
.photo-example-card { .photo-example-card {
@@ -59,34 +59,29 @@
} }
.photo-example-thumb { .photo-example-thumb {
width: 74px; width: 80px;
height: 74px; height: 80px;
display: block; display: block;
overflow: hidden; overflow: hidden;
margin: 0 auto; margin: 0 auto;
border-radius: 12px; border: 2px solid transparent;
border-radius: 14px;
background: #f2f4f7; background: #f2f4f7;
box-sizing: border-box;
} }
.photo-example-image { .photo-example-image {
width: 98px; width: 100%;
height: 150px; height: 100%;
display: block; display: block;
margin: -24px 0 0 -12px;
} }
.example-scene .photo-example-image, .is-positive .photo-example-thumb {
.example-group .photo-example-image { border-color: rgba(22, 191, 118, 0.34);
width: 112px;
margin-left: -18px;
} }
.example-blur .photo-example-image { .is-negative .photo-example-thumb {
filter: blur(1.6px); border-color: rgba(255, 80, 79, 0.34);
}
.example-cover .photo-example-image {
transform: translateY(8px) scale(1.08);
} }
.photo-example-caption { .photo-example-caption {

View File

@@ -3,16 +3,19 @@ export const positiveExamples = [
id: "portrait", id: "portrait",
label: "单人正脸", label: "单人正脸",
variant: "portrait", variant: "portrait",
imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/1.png",
}, },
{ {
id: "smile", id: "smile",
label: "表情自然", label: "表情自然",
variant: "smile", variant: "smile",
imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/2.png",
}, },
{ {
id: "scene", id: "scene",
label: "背景干净", label: "背景干净",
variant: "scene", variant: "scene",
imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/3.png",
}, },
]; ];
@@ -21,15 +24,18 @@ export const negativeExamples = [
id: "blur", id: "blur",
label: "非清晰正脸", label: "非清晰正脸",
variant: "blur", variant: "blur",
imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/4.png",
}, },
{ {
id: "cover", id: "cover",
label: "面部有遮挡", label: "面部有遮挡",
variant: "cover", variant: "cover",
imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/5.png",
}, },
{ {
id: "group", id: "group",
label: "多人合照", label: "多人合照",
variant: "group", variant: "group",
imageUrl: "https://oss.nianxx.cn/mp/static/xiaoqi/6.png",
}, },
]; ];

View File

@@ -1,37 +1,20 @@
<template> <template>
<view class="aigc-use-template-page"> <view class="aigc-use-template-page">
<AigcTopBar <AigcTopBar :points="pointBalance" recharge-label="充值" @back="handleBack" @history="handleHistory"
:points="pointBalance" @recharge="handleRecharge" />
recharge-label="充值"
@back="handleBack"
@history="handleHistory"
@recharge="handleRecharge"
/>
<view class="aigc-use-template-body"> <view class="aigc-use-template-body">
<view class="aigc-use-template-content"> <view class="aigc-use-template-content">
<TemplateVersionHero :template="currentTemplate" /> <TemplateVersionHero :template="currentTemplate" />
<GenerateConfirmPanel <GenerateConfirmPanel v-if="currentStep === 'generateConfirm'" :cost="currentCost" :balance="pointBalance"
v-if="currentStep === 'generateConfirm'" @generate="handleGenerate" @recharge="handleRecharge" />
:cost="currentCost" <GeneratingProgressPanel v-else-if="currentStep === 'generating'" :cost="currentCost" :progress="8"
:balance="pointBalance" @complete="handleGenerateComplete" />
@generate="handleGenerate"
@recharge="handleRecharge"
/>
<GeneratingProgressPanel
v-else-if="currentStep === 'generating'"
:cost="currentCost"
:progress="8"
@complete="handleGenerateComplete"
/>
<template v-else> <template v-else>
<VersionOptionList <VersionOptionList :options="templateItems" :selected-value="selectedTemplateItemId"
:options="templateItems" @select="handleSelectTemplateItem" />
:selected-value="selectedTemplateItemId"
@select="handleSelectTemplateItem"
/>
<text class="aigc-use-template-note"> <text class="aigc-use-template-note">
动效视频版会先生图再升级为 5 秒内动效视频 动效视频版会先生图再升级为 5 秒内动效视频
@@ -41,42 +24,28 @@
</view> </view>
<view v-if="currentStep === 'uploadGuide'" class="aigc-use-template-popup-host"> <view v-if="currentStep === 'uploadGuide'" class="aigc-use-template-popup-host">
<PhotoGuidePanel <PhotoGuidePanel :positive-examples="positiveExamples" :negative-examples="negativeExamples"
:avatar-src="guideAvatar" @close="handleCloseGuide" @confirm="handleConfirmGuide" />
:positive-examples="positiveExamples"
:negative-examples="negativeExamples"
@close="handleCloseGuide"
@confirm="handleConfirmGuide"
/>
</view> </view>
<view v-if="currentStep === 'photoPick'" class="aigc-use-template-popup-host"> <view v-if="currentStep === 'photoPick'" class="aigc-use-template-popup-host">
<PhotoPickDrawer <PhotoPickDrawer :loading="isUploading" @close="handleCloseGuide" @camera="handlePickCamera"
:loading="isUploading" @album="handlePickAlbum" />
@close="handleCloseGuide"
@camera="handlePickCamera"
@album="handlePickAlbum"
/>
</view> </view>
<view v-if="currentStep === 'photoConfirm'" class="aigc-use-template-popup-host"> <view v-if="currentStep === 'photoConfirm'" class="aigc-use-template-popup-host">
<PhotoConfirmDrawer <PhotoConfirmDrawer :avatar-src="selectedImageLocalPath || guideAvatar" @close="handleClosePhotoConfirm"
:avatar-src="selectedImageLocalPath || guideAvatar" @confirm="handleConfirmPhoto" />
@close="handleClosePhotoConfirm"
@confirm="handleConfirmPhoto"
/>
</view> </view>
<PointInsufficientDialog <PointInsufficientDialog v-if="pointDialogVisible" :cost="currentCost" :balance="pointBalance"
v-if="pointDialogVisible" @cancel="handleClosePointDialog" @recharge="handlePointRecharge" />
:cost="currentCost" <Privacy :visible="privacyVisible" :contract-name="privacyContractName" @agree="handlePrivacyAgree"
:balance="pointBalance" @disagree="handlePrivacyDisagree" />
@cancel="handleClosePointDialog"
@recharge="handlePointRecharge"
/>
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app"; import { onLoad, onShow } from "@dcloudio/uni-app";
import Privacy from "@/components/Privacy/index.vue";
import AigcTopBar from "@/pages-aigc/components/AigcTopBar/index.vue"; import AigcTopBar from "@/pages-aigc/components/AigcTopBar/index.vue";
import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js"; import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js";
import { import {
@@ -108,6 +77,9 @@ const currentStep = ref("version");
const pointDialogVisible = ref(false); const pointDialogVisible = ref(false);
const isUploading = ref(false); const isUploading = ref(false);
const isCreatingTask = ref(false); const isCreatingTask = ref(false);
const privacyVisible = ref(false);
const privacyContractName = ref("隐私保护指引");
const pendingImageSourceType = ref("");
const currentTemplate = computed(() => { const currentTemplate = computed(() => {
return templates.value.find((item) => item.templateId === templateId.value) || templates.value[0] || {}; return templates.value.find((item) => item.templateId === templateId.value) || templates.value[0] || {};
@@ -294,9 +266,47 @@ const chooseImageWithSource = (sourceType) => {
}; };
const chooseAndUploadImage = (sourceType) => { const chooseAndUploadImage = (sourceType) => {
if (isUploading.value) return; if (isUploading.value || pendingImageSourceType.value) return;
// #ifdef MP-WEIXIN
pendingImageSourceType.value = sourceType;
wx.getPrivacySetting({
success: (res) => {
if (res?.needAuthorization) {
privacyContractName.value = res.privacyContractName || "隐私保护指引";
privacyVisible.value = true;
return;
}
pendingImageSourceType.value = "";
chooseImageWithSource(sourceType);
},
fail: (error) => {
pendingImageSourceType.value = "";
console.warn("检查微信隐私授权失败", error);
showPlaceholderToast("隐私授权检查失败");
},
});
// #endif
// #ifndef MP-WEIXIN
chooseImageWithSource(sourceType); chooseImageWithSource(sourceType);
// #endif
};
const handlePrivacyAgree = () => {
const sourceType = pendingImageSourceType.value;
privacyVisible.value = false;
pendingImageSourceType.value = "";
if (sourceType) {
chooseImageWithSource(sourceType);
}
};
const handlePrivacyDisagree = () => {
privacyVisible.value = false;
pendingImageSourceType.value = "";
}; };
const handlePickCamera = () => { const handlePickCamera = () => {

View File

@@ -10,10 +10,9 @@ export const devUrl = "https://onefeel.brother7.cn/ingress";
/// 测试服务器wss地址 /// 测试服务器wss地址
export const wssDevUrl = "wss://onefeel.brother7.cn/ingress/agent/ws/chat"; export const wssDevUrl = "wss://onefeel.brother7.cn/ingress/agent/ws/chat";
/// 服务器基础地址 - 根据环境变量自动切换 /// 服务器基础地址 - 根据环境变量自动切换
import { currentClientType } from "@/constant/base"; import { currentClientType } from "@/constant/base";
export const ossChengduUrl = () => { export const ossChengduUrl = () => {
const clientType = currentClientType().toLowerCase(); const clientType = currentClientType().toLowerCase();
return "https://one-feel-config-images-bucket.oss-cn-chengdu.aliyuncs.com/" + clientType + "/" return "https://oss.nianxx.cn/mp/static/" + clientType + "/";
}; };