| @@ -36,10 +36,10 @@ export const userApi = { | |||
| } | |||
| export const inviteApi = { | |||
| getConfig: (params) => request({ url: API.INTERFACE_INVITE_GET_CONFIG, method: 'POST', params }), | |||
| getMy: (params) => request({ url: API.INTERFACE_INVITE_GET_MY, method: 'POST', params }), | |||
| getList: (params) => request({ url: API.INTERFACE_INVITE_GET_LIST, method: 'POST', params }), | |||
| getQrcode: (params) => request({ url: API.INTERFACE_INVITE_GET_QRCODE, method: 'POST', params }) | |||
| getConfig: (params, options = {}) => request({ url: API.INTERFACE_INVITE_GET_CONFIG, method: 'POST', params, ...options }), | |||
| getMy: (params, options = {}) => request({ url: API.INTERFACE_INVITE_GET_MY, method: 'POST', params, ...options }), | |||
| getList: (params, options = {}) => request({ url: API.INTERFACE_INVITE_GET_LIST, method: 'POST', params, ...options }), | |||
| getQrcode: (params, options = {}) => request({ url: API.INTERFACE_INVITE_GET_QRCODE, method: 'POST', params, ...options }) | |||
| } | |||
| export const commonApi = { | |||
| @@ -112,7 +112,7 @@ export const oldPhotoApi = { | |||
| } | |||
| export const toolApi = { | |||
| getMyWorkList: (params) => request({ url: API.INTERFACE_WORK_GET_LIST, method: 'POST', params }), | |||
| getMyWorkList: (params, options = {}) => request({ url: API.INTERFACE_WORK_GET_LIST, method: 'POST', params, ...options }), | |||
| getAppList: (params) => request({ url: API.INTERFACE_APP_GET_LIST, method: 'POST', params }), | |||
| getAppDetail: (params) => request({ url: API.INTERFACE_APP_GET_DETAIL, method: 'POST', params }), | |||
| getWorkList: (params) => request({ url: API.INTERFACE_ART_GET_LIST, method: 'POST', params }), | |||
| @@ -92,6 +92,7 @@ import { onShareAppMessage, onShow } from '@dcloudio/uni-app' | |||
| import { inviteApi } from '@/api/index.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions } from '@/utils/share.js' | |||
| const userStore = useUserStore() | |||
| const { toast } = useApp() | |||
| @@ -113,10 +114,11 @@ onShow(() => { | |||
| loadData() | |||
| }) | |||
| onShareAppMessage(() => ({ | |||
| onShareAppMessage(() => buildShareOptions({ | |||
| title: (share.value && share.value.title) || config.value.share_title || '邀请你体验 AI ONLINE', | |||
| path: sharePath.value, | |||
| imageUrl: (share.value && share.value.image) || config.value.share_image || '' | |||
| imageUrl: (share.value && share.value.image) || config.value.share_image || '', | |||
| source: 'share_invite' | |||
| })) | |||
| async function loadData() { | |||
| @@ -32,6 +32,7 @@ import { onLoad, onShareAppMessage } from '@dcloudio/uni-app' | |||
| import { inviteApi } from '@/api/index.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions } from '@/utils/share.js' | |||
| const userStore = useUserStore() | |||
| const { toast } = useApp() | |||
| @@ -47,10 +48,11 @@ onLoad((options = {}) => { | |||
| loadConfig(options.scene || '') | |||
| }) | |||
| onShareAppMessage(() => ({ | |||
| onShareAppMessage(() => buildShareOptions({ | |||
| title: config.value.share_title || '邀请你体验 AI ONLINE', | |||
| path: `/pages/invite/landing?invite_code=${inviteCode.value}`, | |||
| imageUrl: config.value.share_image || '' | |||
| imageUrl: config.value.share_image || '', | |||
| source: 'share_invite_landing' | |||
| })) | |||
| function parseInviteScene(scene) { | |||
| @@ -107,14 +107,23 @@ | |||
| <script setup> | |||
| import { computed, ref } from 'vue' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShareAppMessage, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { pointApi } from '@/api/index.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions, showPageShareMenu } from '@/utils/share.js' | |||
| const userStore = useUserStore() | |||
| const { toast } = useApp() | |||
| const pageShare = { | |||
| title: '获得点数|签到、邀请、看视频都能拿点数', | |||
| source: 'share_earn_points' | |||
| } | |||
| onShow(() => showPageShareMenu()) | |||
| onShareAppMessage(() => buildShareOptions(pageShare)) | |||
| const loading = ref(true) | |||
| const balance = ref(0) | |||
| const rewarding = ref(false) | |||
| @@ -237,15 +237,25 @@ | |||
| <script setup> | |||
| import { computed, reactive, ref } from 'vue' | |||
| import { onLoad, onShow } from '@dcloudio/uni-app' | |||
| import { onLoad, onShareAppMessage, onShow } from '@dcloudio/uni-app' | |||
| import dayjs from 'dayjs' | |||
| import { billingApi, toolApi, userApi } from '@/api/index.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions, showPageShareMenu } from '@/utils/share.js' | |||
| import LunarCalendar from '@/uni_modules/uview-plus/libs/util/calendar.js' | |||
| const userStore = useUserStore() | |||
| const { toast } = useApp() | |||
| const pageShare = { | |||
| title: 'AI取名|给宝宝取一个有出处的好名字', | |||
| source: 'share_ai_name' | |||
| } | |||
| onShow(() => showPageShareMenu()) | |||
| onShareAppMessage(() => buildShareOptions(pageShare)) | |||
| const appDetail = ref({}) | |||
| const robotInfo = ref({}) | |||
| const todayDate = dayjs().format('YYYY-MM-DD') | |||
| @@ -181,16 +181,25 @@ | |||
| <script setup> | |||
| import { computed, reactive, ref } from 'vue' | |||
| import { onLoad, onShow } from '@dcloudio/uni-app' | |||
| import { onLoad, onShareAppMessage, onShow } from '@dcloudio/uni-app' | |||
| import { billingApi, commonApi, toolApi, userApi } from '@/api/index.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { uploadImage } from '@/utils/upload.js' | |||
| import { buildShareOptions, showPageShareMenu } from '@/utils/share.js' | |||
| const userStore = useUserStore() | |||
| const { toast } = useApp() | |||
| const ANIME_BILLING_MODEL = 'tencent_anime' | |||
| const pageShare = { | |||
| title: 'AI动漫头像|上传自拍生成你的专属头像', | |||
| source: 'share_anime_avatar' | |||
| } | |||
| onShow(() => showPageShareMenu()) | |||
| onShareAppMessage(() => buildShareOptions(pageShare)) | |||
| const appDetail = ref({}) | |||
| const demoList = ref([]) | |||
| const styleList = ref([{ value: '201', name: '默认', model: 2, pic: 'https://cdn.aionline.cc/ai/toolbox/home_card_avatar.png' }]) | |||
| @@ -79,7 +79,7 @@ | |||
| <script setup> | |||
| import { computed, ref } from 'vue' | |||
| import { onLoad, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import dayjs from 'dayjs' | |||
| import { toolApi } from '@/api/index.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| @@ -87,15 +87,25 @@ import { useApp } from '@/utils/useApp.js' | |||
| const { toast } = useApp() | |||
| const list = ref([]) | |||
| const page = ref(1) | |||
| const pageSize = 10 | |||
| const total = ref(0) | |||
| const loading = ref(false) | |||
| const refreshing = ref(false) | |||
| let fetching = false | |||
| let firstShow = true | |||
| let timer = 0 | |||
| const pendingCount = computed(() => list.value.filter((item) => Number(item.is_suc) === 0).length) | |||
| const successCount = computed(() => list.value.filter((item) => Number(item.is_suc) > 0).length) | |||
| onLoad(() => refresh()) | |||
| onLoad(() => fetchList({ reset: true })) | |||
| onShow(() => { | |||
| if (firstShow) { | |||
| firstShow = false | |||
| return | |||
| } | |||
| refreshLoaded(true) | |||
| }) | |||
| onUnload(() => clearTimeout(timer)) | |||
| function formatTime(value) { | |||
| @@ -103,46 +113,73 @@ function formatTime(value) { | |||
| return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm') | |||
| } | |||
| async function fetchList(reset = false) { | |||
| if (loading.value) return | |||
| loading.value = true | |||
| async function fetchList(options = {}) { | |||
| const { | |||
| reset = false, | |||
| silent = false, | |||
| replace = reset, | |||
| size = pageSize | |||
| } = options | |||
| let queryPage = options.pageNo || page.value | |||
| if (fetching) { | |||
| finishRefresh() | |||
| return | |||
| } | |||
| fetching = true | |||
| if (!silent) loading.value = true | |||
| if (reset) { | |||
| page.value = 1 | |||
| queryPage = 1 | |||
| } | |||
| try { | |||
| const res = await toolApi.getAnimeList({ | |||
| page_no: page.value, | |||
| page_size: 10, | |||
| page_no: queryPage, | |||
| page_size: size, | |||
| role: 2 | |||
| }) | |||
| const rows = res.list || [] | |||
| total.value = Number((res.data && res.data.total) || rows.length) | |||
| list.value = reset ? rows : list.value.concat(rows) | |||
| list.value = replace ? rows : list.value.concat(rows) | |||
| scheduleRefresh() | |||
| } catch (e) { | |||
| toast(e.msg || '历史记录加载失败') | |||
| if (!silent) toast(e.msg || '历史记录加载失败') | |||
| } finally { | |||
| fetching = false | |||
| loading.value = false | |||
| refreshing.value = false | |||
| finishRefresh() | |||
| } | |||
| } | |||
| function scheduleRefresh() { | |||
| clearTimeout(timer) | |||
| if (list.value.some((item) => Number(item.is_suc) === 0)) { | |||
| timer = setTimeout(() => refreshSilent(), 2500) | |||
| timer = setTimeout(() => refreshLoaded(true), 2500) | |||
| } | |||
| } | |||
| function finishRefresh() { | |||
| refreshing.value = false | |||
| uni.stopPullDownRefresh() | |||
| } | |||
| function refresh() { | |||
| refreshing.value = true | |||
| refreshSilent() | |||
| fetchList({ reset: true }) | |||
| } | |||
| function refreshSilent() { | |||
| page.value = 1 | |||
| fetchList(true) | |||
| function refreshLoaded(silent = true) { | |||
| const currentPage = Math.max(1, Number(page.value) || 1) | |||
| fetchList({ | |||
| pageNo: 1, | |||
| size: currentPage * pageSize, | |||
| replace: true, | |||
| silent | |||
| }) | |||
| } | |||
| function loadMore() { | |||
| if (loading.value) return | |||
| if (fetching) return | |||
| if (list.value.length >= total.value) return | |||
| page.value += 1 | |||
| fetchList() | |||
| @@ -190,7 +227,7 @@ function deleteItem(item) { | |||
| try { | |||
| await toolApi.deleteAnime({ chat_open_id: item.chat_open_id }) | |||
| toast('删除成功') | |||
| refresh() | |||
| refreshLoaded(true) | |||
| } catch (e) { | |||
| toast(e.msg || '删除失败') | |||
| } | |||
| @@ -91,15 +91,25 @@ import { useApp } from '@/utils/useApp.js' | |||
| const { toast } = useApp() | |||
| const list = ref([]) | |||
| const page = ref(1) | |||
| const pageSize = 10 | |||
| const total = ref(0) | |||
| const loading = ref(false) | |||
| const refreshing = ref(false) | |||
| let fetching = false | |||
| let firstShow = true | |||
| let timer = 0 | |||
| const pendingCount = computed(() => list.value.filter((item) => isPending(item)).length) | |||
| const successCount = computed(() => list.value.filter((item) => Number(item.status) === 1).length) | |||
| onShow(() => refreshSilent()) | |||
| onShow(() => { | |||
| if (firstShow) { | |||
| firstShow = false | |||
| fetchList({ reset: true }) | |||
| return | |||
| } | |||
| refreshLoaded(true) | |||
| }) | |||
| onHide(() => clearTimer()) | |||
| onUnload(() => clearTimer()) | |||
| @@ -112,22 +122,35 @@ function formatTime(value) { | |||
| return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm') | |||
| } | |||
| async function fetchList(reset = false) { | |||
| if (loading.value) { | |||
| async function fetchList(options = {}) { | |||
| const { | |||
| reset = false, | |||
| silent = false, | |||
| replace = reset, | |||
| size = pageSize | |||
| } = options | |||
| let queryPage = options.pageNo || page.value | |||
| if (fetching) { | |||
| finishRefresh() | |||
| return | |||
| } | |||
| loading.value = true | |||
| if (reset) page.value = 1 | |||
| fetching = true | |||
| if (!silent) loading.value = true | |||
| if (reset) { | |||
| page.value = 1 | |||
| queryPage = 1 | |||
| } | |||
| try { | |||
| const res = await idPhotoApi.getList({ page_no: page.value, page_size: 10 }) | |||
| const res = await idPhotoApi.getList({ page_no: queryPage, page_size: size }) | |||
| const rows = res.list || [] | |||
| total.value = Number((res.data && res.data.total) || rows.length) | |||
| list.value = reset ? rows : list.value.concat(rows) | |||
| list.value = replace ? rows : list.value.concat(rows) | |||
| scheduleRefresh() | |||
| } catch (e) { | |||
| toast((e && e.msg) || '记录加载失败') | |||
| if (!silent) toast((e && e.msg) || '记录加载失败') | |||
| } finally { | |||
| fetching = false | |||
| loading.value = false | |||
| finishRefresh() | |||
| } | |||
| @@ -136,7 +159,7 @@ async function fetchList(reset = false) { | |||
| function scheduleRefresh() { | |||
| clearTimer() | |||
| if (list.value.some((item) => isPending(item))) { | |||
| timer = setTimeout(() => refreshSilent(), 3000) | |||
| timer = setTimeout(() => refreshLoaded(true), 3000) | |||
| } | |||
| } | |||
| @@ -152,16 +175,21 @@ function finishRefresh() { | |||
| function refresh() { | |||
| refreshing.value = true | |||
| refreshSilent() | |||
| fetchList({ reset: true }) | |||
| } | |||
| function refreshSilent() { | |||
| page.value = 1 | |||
| fetchList(true) | |||
| function refreshLoaded(silent = true) { | |||
| const currentPage = Math.max(1, Number(page.value) || 1) | |||
| fetchList({ | |||
| pageNo: 1, | |||
| size: currentPage * pageSize, | |||
| replace: true, | |||
| silent | |||
| }) | |||
| } | |||
| function loadMore() { | |||
| if (loading.value || list.value.length >= total.value) return | |||
| if (fetching || list.value.length >= total.value) return | |||
| page.value += 1 | |||
| fetchList() | |||
| } | |||
| @@ -211,7 +239,7 @@ function deleteItem(item) { | |||
| try { | |||
| await idPhotoApi.delete({ photo_open_id: id }) | |||
| toast('删除成功') | |||
| refreshSilent() | |||
| refreshLoaded(true) | |||
| } catch (e) { | |||
| toast((e && e.msg) || '删除失败') | |||
| } | |||
| @@ -284,12 +284,21 @@ | |||
| <script setup> | |||
| import { computed, nextTick, reactive, ref } from 'vue' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShareAppMessage, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { idPhotoApi } from '@/api/index.js' | |||
| import { createIdPhoto } from '@/utils/upload.js' | |||
| import { guid } from '@/utils/util.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions, showPageShareMenu } from '@/utils/share.js' | |||
| const pageShare = { | |||
| title: 'AI证件照|一张自拍制作标准证件照', | |||
| source: 'share_id_photo' | |||
| } | |||
| onShow(() => showPageShareMenu()) | |||
| onShareAppMessage(() => buildShareOptions(pageShare)) | |||
| const STANDARD_BACKGROUNDS = [ | |||
| { key: 'blue', name: '常用蓝', color: '#438edb' }, | |||
| @@ -38,7 +38,7 @@ | |||
| <script setup> | |||
| import { ref } from 'vue' | |||
| import { onLoad, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import dayjs from 'dayjs' | |||
| import { toolApi } from '@/api/index.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| @@ -47,14 +47,25 @@ const { toast } = useApp() | |||
| const robotId = ref('') | |||
| const list = ref([]) | |||
| const page = ref(1) | |||
| const pageSize = 10 | |||
| const total = ref(0) | |||
| const loading = ref(false) | |||
| const refreshing = ref(false) | |||
| let fetching = false | |||
| let firstShow = true | |||
| let timer = 0 | |||
| onLoad((options) => { | |||
| robotId.value = options.robot_id || '' | |||
| refresh() | |||
| fetchList({ reset: true }) | |||
| }) | |||
| onShow(() => { | |||
| if (firstShow) { | |||
| firstShow = false | |||
| return | |||
| } | |||
| refreshLoaded(true) | |||
| }) | |||
| onUnload(() => clearTimeout(timer)) | |||
| @@ -64,47 +75,74 @@ function formatTime(value) { | |||
| return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm') | |||
| } | |||
| async function fetchList(reset = false) { | |||
| if (loading.value) return | |||
| loading.value = true | |||
| async function fetchList(options = {}) { | |||
| const { | |||
| reset = false, | |||
| silent = false, | |||
| replace = reset, | |||
| size = pageSize | |||
| } = options | |||
| let queryPage = options.pageNo || page.value | |||
| if (fetching) { | |||
| finishRefresh() | |||
| return | |||
| } | |||
| fetching = true | |||
| if (!silent) loading.value = true | |||
| if (reset) { | |||
| page.value = 1 | |||
| queryPage = 1 | |||
| } | |||
| try { | |||
| const res = await toolApi.getNameList({ | |||
| page_no: page.value, | |||
| page_size: 10, | |||
| page_no: queryPage, | |||
| page_size: size, | |||
| robot_open_id: robotId.value, | |||
| role: 2 | |||
| }) | |||
| const rows = res.list || [] | |||
| total.value = Number((res.data && res.data.total) || rows.length) | |||
| list.value = reset ? rows : list.value.concat(rows) | |||
| list.value = replace ? rows : list.value.concat(rows) | |||
| scheduleRefresh() | |||
| } catch (e) { | |||
| toast(e.msg || '历史记录加载失败') | |||
| if (!silent) toast(e.msg || '历史记录加载失败') | |||
| } finally { | |||
| fetching = false | |||
| loading.value = false | |||
| refreshing.value = false | |||
| finishRefresh() | |||
| } | |||
| } | |||
| function scheduleRefresh() { | |||
| clearTimeout(timer) | |||
| if (list.value.some((item) => Number(item.is_suc) === 0)) { | |||
| timer = setTimeout(() => refreshSilent(), 2500) | |||
| timer = setTimeout(() => refreshLoaded(true), 2500) | |||
| } | |||
| } | |||
| function finishRefresh() { | |||
| refreshing.value = false | |||
| uni.stopPullDownRefresh() | |||
| } | |||
| function refresh() { | |||
| refreshing.value = true | |||
| refreshSilent() | |||
| fetchList({ reset: true }) | |||
| } | |||
| function refreshSilent() { | |||
| page.value = 1 | |||
| fetchList(true) | |||
| function refreshLoaded(silent = true) { | |||
| const currentPage = Math.max(1, Number(page.value) || 1) | |||
| fetchList({ | |||
| pageNo: 1, | |||
| size: currentPage * pageSize, | |||
| replace: true, | |||
| silent | |||
| }) | |||
| } | |||
| function loadMore() { | |||
| if (loading.value || list.value.length >= total.value) return | |||
| if (fetching || list.value.length >= total.value) return | |||
| page.value += 1 | |||
| fetchList() | |||
| } | |||
| @@ -224,7 +262,7 @@ function deleteItem(item) { | |||
| try { | |||
| await toolApi.deleteName({ chat_open_id: item.chat_open_id }) | |||
| toast('删除成功') | |||
| refresh() | |||
| refreshLoaded(true) | |||
| } catch (e) { | |||
| toast(e.msg || '删除失败') | |||
| } | |||
| @@ -78,7 +78,7 @@ | |||
| <script setup> | |||
| import { computed, ref } from 'vue' | |||
| import { onLoad, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import dayjs from 'dayjs' | |||
| import { oldPhotoApi } from '@/api/index.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| @@ -86,15 +86,25 @@ import { useApp } from '@/utils/useApp.js' | |||
| const { toast } = useApp() | |||
| const list = ref([]) | |||
| const page = ref(1) | |||
| const pageSize = 10 | |||
| const total = ref(0) | |||
| const loading = ref(false) | |||
| const refreshing = ref(false) | |||
| let fetching = false | |||
| let firstShow = true | |||
| let timer = 0 | |||
| const pendingCount = computed(() => list.value.filter((item) => isPending(item)).length) | |||
| const successCount = computed(() => list.value.filter((item) => Number(item.status) === 30).length) | |||
| onLoad(() => refresh()) | |||
| onLoad(() => fetchList({ reset: true })) | |||
| onShow(() => { | |||
| if (firstShow) { | |||
| firstShow = false | |||
| return | |||
| } | |||
| refreshLoaded(true) | |||
| }) | |||
| onUnload(() => clearTimeout(timer)) | |||
| function isPending(item) { | |||
| @@ -106,20 +116,37 @@ function formatTime(value) { | |||
| return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm') | |||
| } | |||
| async function fetchList(reset = false) { | |||
| if (loading.value) return | |||
| loading.value = true | |||
| async function fetchList(options = {}) { | |||
| const { | |||
| reset = false, | |||
| silent = false, | |||
| replace = reset, | |||
| size = pageSize | |||
| } = options | |||
| let queryPage = options.pageNo || page.value | |||
| if (fetching) { | |||
| finishRefresh() | |||
| return | |||
| } | |||
| fetching = true | |||
| if (!silent) loading.value = true | |||
| if (reset) { | |||
| page.value = 1 | |||
| queryPage = 1 | |||
| } | |||
| try { | |||
| const res = await oldPhotoApi.getList({ page_no: page.value, page_size: 10 }) | |||
| const res = await oldPhotoApi.getList({ page_no: queryPage, page_size: size }) | |||
| const rows = res.list || [] | |||
| total.value = Number((res.data && res.data.total) || rows.length) | |||
| list.value = reset ? rows : list.value.concat(rows) | |||
| list.value = replace ? rows : list.value.concat(rows) | |||
| scheduleRefresh() | |||
| } catch (e) { | |||
| toast((e && e.msg) || '历史记录加载失败') | |||
| if (!silent) toast((e && e.msg) || '历史记录加载失败') | |||
| } finally { | |||
| fetching = false | |||
| loading.value = false | |||
| refreshing.value = false | |||
| finishRefresh() | |||
| } | |||
| } | |||
| @@ -127,22 +154,32 @@ async function fetchList(reset = false) { | |||
| function scheduleRefresh() { | |||
| clearTimeout(timer) | |||
| if (list.value.some((item) => isPending(item))) { | |||
| timer = setTimeout(() => refreshSilent(), 3000) | |||
| timer = setTimeout(() => refreshLoaded(true), 3000) | |||
| } | |||
| } | |||
| function finishRefresh() { | |||
| refreshing.value = false | |||
| uni.stopPullDownRefresh() | |||
| } | |||
| function refresh() { | |||
| refreshing.value = true | |||
| refreshSilent() | |||
| fetchList({ reset: true }) | |||
| } | |||
| function refreshSilent() { | |||
| page.value = 1 | |||
| fetchList(true) | |||
| function refreshLoaded(silent = true) { | |||
| const currentPage = Math.max(1, Number(page.value) || 1) | |||
| fetchList({ | |||
| pageNo: 1, | |||
| size: currentPage * pageSize, | |||
| replace: true, | |||
| silent | |||
| }) | |||
| } | |||
| function loadMore() { | |||
| if (loading.value) return | |||
| if (fetching) return | |||
| if (list.value.length >= total.value) return | |||
| page.value += 1 | |||
| fetchList() | |||
| @@ -185,7 +222,7 @@ function deleteItem(item) { | |||
| try { | |||
| await oldPhotoApi.delete({ photo_open_id: item.photo_open_id }) | |||
| toast('删除成功') | |||
| refresh() | |||
| refreshLoaded(true) | |||
| } catch (e) { | |||
| toast((e && e.msg) || '删除失败') | |||
| } | |||
| @@ -171,16 +171,25 @@ | |||
| <script setup> | |||
| import { computed, ref } from 'vue' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShareAppMessage, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { commonApi, oldPhotoApi, userApi } from '@/api/index.js' | |||
| import { createOldPhoto } from '@/utils/upload.js' | |||
| import { guid } from '@/utils/util.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions, showPageShareMenu } from '@/utils/share.js' | |||
| const userStore = useUserStore() | |||
| const { toast } = useApp() | |||
| const pageShare = { | |||
| title: '老照片修复上色|让旧照片焕然一新', | |||
| source: 'share_old_photo' | |||
| } | |||
| onShow(() => showPageShareMenu()) | |||
| onShareAppMessage(() => buildShareOptions(pageShare)) | |||
| // 轮询:混元未买并发,排队 + 生成通常几十秒,最长按后端 20 分钟超时来兜 | |||
| const POLL_INTERVAL = 3000 | |||
| const POLL_LIMIT = 5 * 60 * 1000 | |||
| @@ -79,7 +79,7 @@ | |||
| <script setup> | |||
| import { computed, ref } from 'vue' | |||
| import { onLoad, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import dayjs from 'dayjs' | |||
| import { portraitApi } from '@/api/index.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| @@ -87,9 +87,12 @@ import { useApp } from '@/utils/useApp.js' | |||
| const { toast } = useApp() | |||
| const list = ref([]) | |||
| const page = ref(1) | |||
| const pageSize = 10 | |||
| const total = ref(0) | |||
| const loading = ref(false) | |||
| const refreshing = ref(false) | |||
| let fetching = false | |||
| let firstShow = true | |||
| let timer = 0 | |||
| const pendingCount = computed(() => list.value.filter((item) => isPending(item)).length) | |||
| @@ -97,7 +100,14 @@ const photoCount = computed(() => | |||
| list.value.reduce((sum, item) => sum + Number(item.success_count || 0), 0) | |||
| ) | |||
| onLoad(() => refresh()) | |||
| onLoad(() => fetchList({ reset: true })) | |||
| onShow(() => { | |||
| if (firstShow) { | |||
| firstShow = false | |||
| return | |||
| } | |||
| refreshLoaded(true) | |||
| }) | |||
| onUnload(() => clearTimeout(timer)) | |||
| function isPending(item) { | |||
| @@ -109,20 +119,37 @@ function formatTime(value) { | |||
| return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm') | |||
| } | |||
| async function fetchList(reset = false) { | |||
| if (loading.value) return | |||
| loading.value = true | |||
| async function fetchList(options = {}) { | |||
| const { | |||
| reset = false, | |||
| silent = false, | |||
| replace = reset, | |||
| size = pageSize | |||
| } = options | |||
| let queryPage = options.pageNo || page.value | |||
| if (fetching) { | |||
| finishRefresh() | |||
| return | |||
| } | |||
| fetching = true | |||
| if (!silent) loading.value = true | |||
| if (reset) { | |||
| page.value = 1 | |||
| queryPage = 1 | |||
| } | |||
| try { | |||
| const res = await portraitApi.getList({ page_no: page.value, page_size: 10 }) | |||
| const res = await portraitApi.getList({ page_no: queryPage, page_size: size }) | |||
| const rows = res.list || [] | |||
| total.value = Number((res.data && res.data.total) || rows.length) | |||
| list.value = reset ? rows : list.value.concat(rows) | |||
| list.value = replace ? rows : list.value.concat(rows) | |||
| scheduleRefresh() | |||
| } catch (e) { | |||
| toast((e && e.msg) || '记录加载失败') | |||
| if (!silent) toast((e && e.msg) || '记录加载失败') | |||
| } finally { | |||
| fetching = false | |||
| loading.value = false | |||
| refreshing.value = false | |||
| finishRefresh() | |||
| } | |||
| } | |||
| @@ -130,22 +157,32 @@ async function fetchList(reset = false) { | |||
| function scheduleRefresh() { | |||
| clearTimeout(timer) | |||
| if (list.value.some((item) => isPending(item))) { | |||
| timer = setTimeout(() => refreshSilent(), 3000) | |||
| timer = setTimeout(() => refreshLoaded(true), 3000) | |||
| } | |||
| } | |||
| function finishRefresh() { | |||
| refreshing.value = false | |||
| uni.stopPullDownRefresh() | |||
| } | |||
| function refresh() { | |||
| refreshing.value = true | |||
| refreshSilent() | |||
| fetchList({ reset: true }) | |||
| } | |||
| function refreshSilent() { | |||
| page.value = 1 | |||
| fetchList(true) | |||
| function refreshLoaded(silent = true) { | |||
| const currentPage = Math.max(1, Number(page.value) || 1) | |||
| fetchList({ | |||
| pageNo: 1, | |||
| size: currentPage * pageSize, | |||
| replace: true, | |||
| silent | |||
| }) | |||
| } | |||
| function loadMore() { | |||
| if (loading.value) return | |||
| if (fetching) return | |||
| if (list.value.length >= total.value) return | |||
| page.value += 1 | |||
| fetchList() | |||
| @@ -187,7 +224,7 @@ function deleteItem(item) { | |||
| try { | |||
| await portraitApi.delete({ photo_open_id: item.photo_open_id }) | |||
| toast('删除成功') | |||
| refresh() | |||
| refreshLoaded(true) | |||
| } catch (e) { | |||
| toast((e && e.msg) || '删除失败') | |||
| } | |||
| @@ -270,16 +270,25 @@ | |||
| <script setup> | |||
| import { computed, reactive, ref, watch } from 'vue' | |||
| import { onLoad, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { onLoad, onShareAppMessage, onShow, onUnload } from '@dcloudio/uni-app' | |||
| import { commonApi, portraitApi, userApi } from '@/api/index.js' | |||
| import { createPortrait } from '@/utils/upload.js' | |||
| import { guid } from '@/utils/util.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions, showPageShareMenu } from '@/utils/share.js' | |||
| const userStore = useUserStore() | |||
| const { toast } = useApp() | |||
| const pageShare = { | |||
| title: 'AI形象照|职业照、艺术写真一键生成', | |||
| source: 'share_portrait_photo' | |||
| } | |||
| onShow(() => showPageShareMenu()) | |||
| onShareAppMessage(() => buildShareOptions(pageShare)) | |||
| const POLL_INTERVAL = 3000 | |||
| const POLL_LIMIT = 8 * 60 * 1000 | |||
| @@ -49,15 +49,24 @@ | |||
| <script setup> | |||
| import { ref } from 'vue' | |||
| import { onLoad } from '@dcloudio/uni-app' | |||
| import { onLoad, onShareAppMessage, onShow } from '@dcloudio/uni-app' | |||
| import { commonApi } from '@/api/index.js' | |||
| import { useApp } from '@/utils/useApp.js' | |||
| import { buildShareOptions, showPageShareMenu } from '@/utils/share.js' | |||
| const { toast } = useApp() | |||
| const refreshing = ref(false) | |||
| const bannerIndex = ref(0) | |||
| const AI_CDN_BASE = 'https://cdn.aionline.cc/ai' | |||
| const pageShare = { | |||
| title: 'AI在线|开箱即用的 AI 工具箱', | |||
| source: 'share_home' | |||
| } | |||
| onShow(() => showPageShareMenu()) | |||
| onShareAppMessage(() => buildShareOptions(pageShare)) | |||
| const defaultBanners = [] | |||
| const defaultRecommends = [ | |||
| @@ -122,6 +122,7 @@ const refreshing = ref(false) | |||
| const counts = ref({ all: 0, ainame: 0, anime: 0, image: 0, photo: 0, oldphoto: 0, portrait: 0 }) | |||
| // 类型标签栏的横向滚动位置 | |||
| const tabsScrollLeft = ref(0) | |||
| let fetching = false | |||
| let timer = 0 | |||
| const tabs = computed(() => [ | |||
| @@ -135,11 +136,17 @@ const tabs = computed(() => [ | |||
| onShow(() => { | |||
| const preferredTab = uni.getStorageSync('works_active_tab') | |||
| let needReset = false | |||
| if (tabs.value.some((item) => item.key === preferredTab)) { | |||
| needReset = activeTab.value !== preferredTab | |||
| activeTab.value = preferredTab | |||
| uni.removeStorageSync('works_active_tab') | |||
| } | |||
| refreshSilent() | |||
| if (needReset) { | |||
| fetchList({ reset: true }) | |||
| } else { | |||
| refreshLoaded(list.value.length > 0) | |||
| } | |||
| // 从其它页面带着类型进来时,把该类型滚到可视区中间(等渲染完成再量位置) | |||
| nextTick(() => centerTab(activeTab.value)) | |||
| }) | |||
| @@ -152,7 +159,15 @@ function formatTime(value) { | |||
| return dayjs(Number(value) * 1000).format('YYYY-MM-DD HH:mm') | |||
| } | |||
| async function fetchList(reset = false, silent = false) { | |||
| async function fetchList(options = {}) { | |||
| const { | |||
| reset = false, | |||
| silent = false, | |||
| replace = reset, | |||
| size = pageSize | |||
| } = options | |||
| let queryPage = options.pageNo || page.value | |||
| if (!userStore.isLogin) { | |||
| list.value = [] | |||
| total.value = 0 | |||
| @@ -160,27 +175,48 @@ async function fetchList(reset = false, silent = false) { | |||
| uni.stopPullDownRefresh() | |||
| return | |||
| } | |||
| if (loading.value) { | |||
| if (fetching) { | |||
| if (refreshing.value) uni.stopPullDownRefresh() | |||
| return | |||
| } | |||
| loading.value = true | |||
| if (reset) page.value = 1 | |||
| fetching = true | |||
| if (!silent) loading.value = true | |||
| if (reset) { | |||
| page.value = 1 | |||
| queryPage = 1 | |||
| } | |||
| try { | |||
| const res = await toolApi.getMyWorkList({ | |||
| type: activeTab.value, | |||
| page_no: page.value, | |||
| page_size: pageSize | |||
| }) | |||
| page_no: queryPage, | |||
| page_size: size | |||
| }, { authSilent: true }) | |||
| const rows = res.list || [] | |||
| const data = res.data || {} | |||
| list.value = reset ? rows : list.value.concat(rows) | |||
| if (res.authExpired || data.authExpired) { | |||
| userStore.clear() | |||
| list.value = [] | |||
| total.value = 0 | |||
| counts.value = normalizeCounts({}) | |||
| clearTimer() | |||
| return | |||
| } | |||
| list.value = replace ? rows : list.value.concat(rows) | |||
| total.value = Number(data.total || rows.length) | |||
| counts.value = normalizeCounts(data.counts || {}) | |||
| scheduleRefresh() | |||
| } catch (e) { | |||
| if (Number(e?.code) === 1009) { | |||
| userStore.clear() | |||
| list.value = [] | |||
| total.value = 0 | |||
| counts.value = normalizeCounts({}) | |||
| clearTimer() | |||
| return | |||
| } | |||
| if (!silent) toast(e.msg || '作品加载失败') | |||
| } finally { | |||
| fetching = false | |||
| loading.value = false | |||
| refreshing.value = false | |||
| uni.stopPullDownRefresh() | |||
| @@ -199,8 +235,14 @@ function normalizeCounts(data = {}) { | |||
| } | |||
| } | |||
| function refreshSilent(silent = false) { | |||
| fetchList(true, silent) | |||
| function refreshLoaded(silent = true) { | |||
| const currentPage = Math.max(1, Number(page.value) || 1) | |||
| fetchList({ | |||
| pageNo: 1, | |||
| size: currentPage * pageSize, | |||
| replace: true, | |||
| silent | |||
| }) | |||
| } | |||
| function onRefresh() { | |||
| @@ -213,11 +255,11 @@ function onRefresh() { | |||
| }) | |||
| return | |||
| } | |||
| refreshSilent() | |||
| fetchList({ reset: true }) | |||
| } | |||
| function loadMore() { | |||
| if (loading.value || list.value.length >= total.value) return | |||
| if (fetching || list.value.length >= total.value) return | |||
| page.value += 1 | |||
| fetchList() | |||
| } | |||
| @@ -226,7 +268,7 @@ function changeTab(key) { | |||
| centerTab(key) | |||
| if (activeTab.value === key) return | |||
| activeTab.value = key | |||
| refreshSilent() | |||
| fetchList({ reset: true }) | |||
| } | |||
| /** | |||
| @@ -255,7 +297,7 @@ function centerTab(key) { | |||
| function scheduleRefresh() { | |||
| clearTimer() | |||
| if (list.value.some((item) => Number(item.status) === 0)) { | |||
| timer = setTimeout(() => refreshSilent(true), 3000) | |||
| timer = setTimeout(() => refreshLoaded(true), 3000) | |||
| } | |||
| } | |||
| @@ -9,10 +9,14 @@ const DEFAULT_ENV = 'pro' | |||
| const ENV_MAP = { | |||
| dev: { | |||
| BASE_URL: 'http://192.168.31.168:16888', | |||
| M_URL: 'https://m.ai.jiefuku.com', | |||
| PC_URL: 'https://ai.jiefuku.com', | |||
| ENV_KEY: 'dev' | |||
| // BASE_URL: 'http://192.168.31.168:16888', | |||
| // M_URL: 'https://m.ai.jiefuku.com', | |||
| // PC_URL: 'https://ai.jiefuku.com', | |||
| // ENV_KEY: 'dev', | |||
| BASE_URL: 'https://api.aionline.cc', | |||
| M_URL: 'https://m.aionline.cc', | |||
| PC_URL: 'https://tool.aionline.cc', | |||
| ENV_KEY: 'pro' | |||
| }, | |||
| test: { | |||
| BASE_URL: 'https://api.jiefuku.com', | |||
| @@ -1,4 +1,5 @@ | |||
| import config from './config.js' | |||
| import { clearAuth } from './auth.js' | |||
| import { guid, getNonce, getVersion, getClient, isLoginTop } from './util.js' | |||
| let BASE_URL = config.BASE_URL | |||
| @@ -7,18 +8,38 @@ let BASE_URL = config.BASE_URL | |||
| BASE_URL = '/api' | |||
| // #endif | |||
| function clearLoginState() { | |||
| clearAuth() | |||
| // 动态引入避免和 Pinia store 形成循环依赖;storage 先同步清掉,页面状态随后刷新。 | |||
| import('@/store/user.js').then(({ useUserStore }) => { | |||
| useUserStore().clear() | |||
| }).catch(() => {}) | |||
| } | |||
| function normalizeResponse(response) { | |||
| // uni-app Promise 形式在部分端可能返回 [err, res],这里保留兼容; | |||
| // 主流程已改用 success/fail callback,避免小程序端 response.data 解析不稳定。 | |||
| if (Array.isArray(response)) { | |||
| const [err, res] = response | |||
| if (err) throw err | |||
| return res || {} | |||
| } | |||
| return response || {} | |||
| } | |||
| /** | |||
| * 统一请求封装(沿用旧版鉴权约定) | |||
| * - 自动附加 token / uid / mac / client / source / version 等公共参数 | |||
| * - 响应 code===0 视为成功;code===1009 登录失效,清空登录态并跳转登录 | |||
| * @param {{ url: string, method?: string, params?: object, headers?: object, loading?: boolean }} options | |||
| * @param {{ url: string, method?: string, params?: object, headers?: object, loading?: boolean, authSilent?: boolean }} options | |||
| */ | |||
| export default function request({ | |||
| url = '', | |||
| method = 'GET', | |||
| params = {}, | |||
| headers = {}, | |||
| loading = false | |||
| loading = false, | |||
| authSilent = false | |||
| } = {}) { | |||
| const fullUrl = BASE_URL + url | |||
| const timestamp = parseInt(Date.now() / 1000) | |||
| @@ -66,40 +87,63 @@ export default function request({ | |||
| if (loading) uni.showLoading({ mask: true, title: '加载中...' }) | |||
| return new Promise((resolve, reject) => { | |||
| const finish = () => { | |||
| if (loading) uni.hideLoading() | |||
| } | |||
| const handleResponse = (response) => { | |||
| const normalized = normalizeResponse(response) | |||
| const res = (normalized && normalized.data) || {} | |||
| if (res.code === 0) { | |||
| resolve(res) | |||
| return | |||
| } | |||
| if (res.code === 1009) { | |||
| clearLoginState() | |||
| if (authSilent) { | |||
| resolve({ | |||
| code: 0, | |||
| data: { authExpired: true }, | |||
| list: [], | |||
| msg: '', | |||
| authExpired: true, | |||
| raw: res | |||
| }) | |||
| return | |||
| } | |||
| if (!isLoginTop()) { | |||
| let loginUrl = '/pages/login/login' | |||
| // #ifdef H5 | |||
| loginUrl += `?redirect=${encodeURIComponent(location.pathname + location.search)}` | |||
| // #endif | |||
| uni.navigateTo({ url: loginUrl }) | |||
| } | |||
| reject(Object.assign({}, res, { authExpired: true })) | |||
| return | |||
| } | |||
| reject(res) | |||
| } | |||
| uni.request({ | |||
| url: fullUrl, | |||
| method, | |||
| header, | |||
| data | |||
| }) | |||
| .then((response) => { | |||
| const res = (response && response.data) || {} | |||
| if (res.code === 0) { | |||
| resolve(res) | |||
| } else if (res.code === 1009) { | |||
| // 登录失效:清空登录态并跳转登录页 | |||
| // 动态引入避免循环依赖 | |||
| import('@/store/user.js').then(({ useUserStore }) => { | |||
| useUserStore().clear() | |||
| }) | |||
| if (!isLoginTop()) { | |||
| let loginUrl = '/pages/login/login' | |||
| // #ifdef H5 | |||
| loginUrl += `?redirect=${encodeURIComponent(location.pathname + location.search)}` | |||
| // #endif | |||
| uni.navigateTo({ url: loginUrl }) | |||
| } | |||
| reject(res) | |||
| } else { | |||
| reject(res) | |||
| data, | |||
| success: (response) => { | |||
| try { | |||
| handleResponse(response) | |||
| } catch (e) { | |||
| const status = e && e.statusCode | |||
| reject({ code: 1, msg: status ? `网络异常 (${status})` : '网络异常,请重试!' }) | |||
| } finally { | |||
| finish() | |||
| } | |||
| }) | |||
| .catch((e) => { | |||
| }, | |||
| fail: (e) => { | |||
| const status = e && e.statusCode | |||
| reject({ code: 1, msg: status ? `网络异常 (${status})` : '网络异常,请重试!' }) | |||
| }) | |||
| .finally(() => { | |||
| if (loading) uni.hideLoading() | |||
| }) | |||
| finish() | |||
| } | |||
| }) | |||
| }) | |||
| } | |||
| @@ -0,0 +1,99 @@ | |||
| import { inviteApi } from '@/api/index.js' | |||
| import { useUserStore } from '@/store/user.js' | |||
| const DEFAULT_SHARE = { | |||
| title: 'AI在线|开箱即用的 AI 工具箱', | |||
| imageUrl: '', | |||
| path: '/pages/toolbox/toolbox' | |||
| } | |||
| const shareState = { | |||
| loaded: false, | |||
| loading: null, | |||
| token: '', | |||
| share: {}, | |||
| config: {} | |||
| } | |||
| function appendQuery(path, query = {}) { | |||
| let nextPath = path || DEFAULT_SHARE.path | |||
| const pairs = Object.keys(query) | |||
| .filter((key) => query[key] !== undefined && query[key] !== null && query[key] !== '') | |||
| .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(query[key])}`) | |||
| if (!pairs.length) return nextPath | |||
| nextPath += nextPath.includes('?') ? '&' : '?' | |||
| return nextPath + pairs.join('&') | |||
| } | |||
| function getInviteCode() { | |||
| const userStore = useUserStore() | |||
| userStore.restore() | |||
| const user = userStore.userInfo || {} | |||
| return user.open_id || user.user_open_id || user.invite_code || uni.getStorageSync('open_id') || '' | |||
| } | |||
| export function getInviteSharePath(options = {}) { | |||
| const inviteCode = getInviteCode() | |||
| // 与邀请好友页保持同一落地逻辑:统一进入邀请落地页。 | |||
| // 未登录时 invite_code 为空,好友仍能进入活动说明;登录用户会携带自己的 open_id。 | |||
| const path = `/pages/invite/landing?invite_code=${encodeURIComponent(inviteCode)}` | |||
| return appendQuery(path, { source: options.source }) | |||
| } | |||
| export async function preloadInviteShare() { | |||
| const userStore = useUserStore() | |||
| userStore.restore() | |||
| const currentToken = userStore.token || '' | |||
| if (!currentToken) { | |||
| shareState.loaded = true | |||
| shareState.token = '' | |||
| shareState.share = {} | |||
| shareState.config = {} | |||
| return shareState | |||
| } | |||
| if (shareState.loaded && shareState.token === currentToken) return shareState | |||
| if (shareState.loading) return shareState.loading | |||
| shareState.loading = inviteApi.getMy({}, { authSilent: true }) | |||
| .then((res) => { | |||
| const data = res.data || {} | |||
| shareState.share = data.share || {} | |||
| shareState.config = data.config || {} | |||
| shareState.token = currentToken | |||
| shareState.loaded = true | |||
| return shareState | |||
| }) | |||
| .catch(() => { | |||
| shareState.loaded = true | |||
| return shareState | |||
| }) | |||
| .finally(() => { | |||
| shareState.loading = null | |||
| }) | |||
| return shareState.loading | |||
| } | |||
| export function buildShareOptions(options = {}) { | |||
| const share = shareState.share || {} | |||
| const config = shareState.config || {} | |||
| const path = appendQuery( | |||
| options.path || share.path || getInviteSharePath(), | |||
| { source: options.source } | |||
| ) | |||
| return { | |||
| title: options.title || share.title || config.share_title || DEFAULT_SHARE.title, | |||
| path, | |||
| imageUrl: options.imageUrl || share.image || config.share_image || DEFAULT_SHARE.imageUrl | |||
| } | |||
| } | |||
| export function showPageShareMenu() { | |||
| preloadInviteShare() | |||
| try { | |||
| uni.showShareMenu({ withShareTicket: true }) | |||
| } catch (e) {} | |||
| } | |||