| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- <template>
- <view class="acct-pwd-page">
- <view class="acct-pwd-nav" :style="{ paddingTop: statusBar + 'px' }">
- <view class="acct-pwd-nav-row">
- <view class="acct-pwd-back" @tap="goBack">
- <text class="acct-pwd-back-icon">←</text>
- </view>
- <text class="acct-pwd-nav-title">{{ copy.pageTitle }}</text>
- <view class="fb-nav-link" @tap="openMyList">
- <text class="fb-nav-link-txt">{{ copy.myList }}</text>
- </view>
- </view>
- </view>
- <scroll-view
- scroll-y
- class="acct-pwd-scroll"
- :style="{ height: scrollHeight + 'px' }"
- >
- <view class="acct-pwd-body" :style="{ paddingBottom: footerPad + 'px' }">
- <view class="acct-pwd-hero">
- <view class="acct-pwd-hero-icon-wrap">
- <text class="acct-pwd-hero-icon">💬</text>
- </view>
- <text class="acct-pwd-hero-title">{{ copy.heroTitle }}</text>
- <text class="acct-pwd-hero-desc">{{ copy.heroDesc }}</text>
- </view>
- <view class="fb-section-row">
- <text class="cp-section">{{ copy.sectionContent }}</text>
- <text class="fb-quick" @tap="chooseQuickText">{{ copy.quickInput }}</text>
- </view>
- <view class="acct-pwd-card">
- <textarea
- v-model="form.content"
- class="cp-textarea"
- maxlength="-1"
- :placeholder="copy.contentPlaceholder"
- placeholder-class="cp-textarea-placeholder"
- />
- </view>
- <text class="cp-section">{{ copy.sectionScreenshots }}</text>
- <view class="acct-pwd-card">
- <view class="cp-img-head">
- <text class="cp-img-head-label">{{ copy.previewImages }}</text>
- <text class="cp-img-head-count">{{ form.images.length }}/{{ maxImages }}</text>
- </view>
- <view class="cp-img-grid">
- <view
- v-for="(image, index) in form.images"
- :key="index"
- class="cp-img-cell"
- >
- <image
- class="cp-img-thumb"
- :src="thumb(image)"
- mode="aspectFill"
- @tap="previewImages(index)"
- />
- <view class="cp-img-remove" @tap.stop="removeImage(index)">
- <text class="cp-img-remove-icon">×</text>
- </view>
- </view>
- <view
- v-if="form.images.length < maxImages"
- class="cp-img-cell cp-img-cell--add"
- @tap="pickImages"
- >
- <text class="cp-img-add-icon">+</text>
- <text class="cp-img-add-txt">{{ copy.addImage }}</text>
- </view>
- </view>
- </view>
- <text class="cp-section">{{ copy.sectionContact }}</text>
- <view class="acct-pwd-card fb-contact-card">
- <input
- v-model="form.contact"
- class="fb-contact-input"
- :placeholder="copy.contactPlaceholder"
- placeholder-class="cp-textarea-placeholder"
- />
- </view>
- <view class="acct-pwd-card fb-rate-card">
- <text class="fb-rate-label">{{ copy.sectionRating }}</text>
- <view class="fb-stars">
- <text
- v-for="n in 5"
- :key="n"
- class="fb-star"
- :class="{ 'fb-star--on': form.score >= n }"
- @tap="setScore(n)"
- >★</text>
- </view>
- </view>
- </view>
- </scroll-view>
- <view class="cp-footer" :style="{ paddingBottom: safeBottom + 'px' }">
- <button
- class="acct-pwd-submit"
- :disabled="submitting"
- :loading="submitting"
- :class="{ 'acct-pwd-submit--busy': submitting }"
- @tap="submit"
- >
- <text class="acct-pwd-submit__text">{{ submitting ? copy.submitting : copy.submit }}</text>
- </button>
- </view>
- </view>
- </template>
- <script>
- import {
- submitFeedback,
- uploadFeedbackImage,
- buildDeviceInfo
- } from '@/features/account/feedback-gateway'
- export default {
- data() {
- return {
- statusBar: 0,
- scrollHeight: 600,
- safeBottom: 0,
- footerPad: 120,
- maxImages: 8,
- submitting: false,
- uploading: false,
- form: {
- score: 0,
- content: '',
- images: [],
- contact: ''
- }
- }
- },
- computed: {
- copy() {
- const t = this.$t.bind(this)
- return {
- pageTitle: t('user.feedback'),
- myList: t('feedback.myList'),
- heroTitle: t('feedback.heroTitle'),
- heroDesc: t('feedback.heroDesc'),
- sectionContent: t('feedback.questionAndSuggestion'),
- quickInput: t('feedback.quickInput'),
- contentPlaceholder: t('feedback.enterDescription'),
- sectionScreenshots: t('feedback.problemScreenshots'),
- previewImages: t('feedback.clickToPreview'),
- addImage: t('feedback.addImage'),
- sectionContact: t('feedback.contactInfo'),
- contactPlaceholder: t('feedback.contactPlaceholder'),
- sectionRating: t('feedback.appRating'),
- submit: t('feedback.submit'),
- submitting: t('feedback.submitting'),
- contentRequired: t('feedback.contentRequired'),
- submitOk: t('feedback.submitOk'),
- network: t('user.realnameNetworkError'),
- uploading: t('feedback.uploading')
- }
- },
- quickIssues() {
- return [
- this.$t('feedback.issue1'),
- this.$t('feedback.issue2'),
- this.$t('feedback.issue3'),
- this.$t('feedback.issue4')
- ]
- }
- },
- onLoad() {
- const sys = uni.getSystemInfoSync()
- this.statusBar = sys.statusBarHeight || 0
- this.safeBottom = sys.safeAreaInsets ? sys.safeAreaInsets.bottom : 0
- const navH = this.statusBar + uni.upx2px(88)
- this.scrollHeight = (sys.windowHeight || 667) - navH - uni.upx2px(120)
- this.footerPad = uni.upx2px(120) + this.safeBottom
- },
- methods: {
- goBack() {
- if (getCurrentPages().length > 1) {
- uni.navigateBack({ delta: 1 })
- } else {
- uni.switchTab({
- url: '/pages/mine/index',
- fail: () => uni.reLaunch({ url: '/pages/mine/index' })
- })
- }
- },
- openMyList() {
- uni.navigateTo({ url: '/pages/account/feedback-lists' })
- },
- thumb(url) {
- return this.$appKit && this.$appKit.oss ? this.$appKit.oss(url, 200, 200) : url
- },
- setScore(n) {
- this.form.score = n
- },
- chooseQuickText() {
- uni.showActionSheet({
- itemList: this.quickIssues,
- success: (res) => {
- const text = this.quickIssues[res.tapIndex]
- if (text) this.form.content = text
- }
- })
- },
- pickImages() {
- if (this.uploading) return
- const remain = this.maxImages - this.form.images.length
- if (remain <= 0) return
- uni.chooseImage({
- sourceType: ['camera', 'album'],
- sizeType: ['compressed'],
- count: remain,
- success: (res) => {
- this.uploadChosen(res.tempFilePaths || [])
- }
- })
- },
- async uploadChosen(paths) {
- if (!paths.length) return
- this.uploading = true
- uni.showLoading({ mask: true, title: this.copy.uploading })
- try {
- for (const path of paths) {
- const url = await uploadFeedbackImage(path)
- if (url) this.form.images.push(url)
- }
- } catch (err) {
- this.$appKit.msg((err && err.message) || this.copy.network)
- } finally {
- this.uploading = false
- uni.hideLoading()
- }
- },
- removeImage(index) {
- this.form.images.splice(index, 1)
- },
- previewImages(index) {
- const urls = this.form.images.map((item) =>
- this.$appKit.oss ? this.$appKit.oss(item, 500) : item
- )
- uni.previewImage({
- urls,
- current: urls[index],
- indicator: 'number'
- })
- },
- async submit() {
- if (this.submitting) return
- if (!String(this.form.content || '').trim()) {
- this.$appKit.msg(this.copy.contentRequired)
- return
- }
- this.submitting = true
- uni.showLoading({ mask: true })
- try {
- await submitFeedback({
- score: this.form.score,
- content: String(this.form.content).trim(),
- images: this.form.images,
- contact: String(this.form.contact || '').trim(),
- device: buildDeviceInfo()
- })
- this.$appKit.msg(this.copy.submitOk)
- setTimeout(() => {
- uni.navigateTo({ url: '/pages/account/feedback-lists' })
- }, 400)
- } catch (err) {
- this.$appKit.msg((err && err.message) || this.copy.network)
- } finally {
- this.submitting = false
- uni.hideLoading()
- }
- }
- }
- }
- </script>
- <style scoped>
- @import './account-flow.css';
- .fb-nav-link {
- min-width: 64rpx;
- padding: 8rpx 0;
- }
- .fb-nav-link-txt {
- font-size: 26rpx;
- color: #4a8cff;
- font-weight: 500;
- }
- .fb-section-row {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- padding: 0 8rpx 16rpx;
- }
- .fb-quick {
- font-size: 26rpx;
- color: #4a8cff;
- font-weight: 500;
- }
- .fb-contact-card {
- padding: 8rpx 28rpx;
- }
- .fb-contact-input {
- width: 100%;
- height: 88rpx;
- font-size: 30rpx;
- color: #1a1d26;
- }
- .fb-rate-card {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- padding: 28rpx;
- }
- .fb-rate-label {
- font-size: 30rpx;
- color: #1a1d26;
- font-weight: 500;
- }
- .fb-stars {
- display: flex;
- flex-direction: row;
- }
- .fb-star {
- font-size: 48rpx;
- color: #d5dae6;
- line-height: 1;
- margin-left: 8rpx;
- }
- .fb-star--on {
- color: #f59e0b;
- }
- .cp-section {
- display: block;
- padding: 0 8rpx 16rpx;
- font-size: 26rpx;
- color: #8a8d9e;
- }
- .cp-textarea {
- width: 100%;
- min-height: 220rpx;
- font-size: 28rpx;
- color: #1a1d26;
- line-height: 1.5;
- }
- .cp-textarea-placeholder {
- color: #b8bcc8;
- }
- .cp-img-head {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 20rpx;
- }
- .cp-img-head-label {
- font-size: 28rpx;
- color: #1a1d26;
- font-weight: 500;
- }
- .cp-img-head-count {
- font-size: 24rpx;
- color: #8a8d9e;
- }
- .cp-img-grid {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- margin: -8rpx;
- }
- .cp-img-cell {
- position: relative;
- width: calc(25% - 16rpx);
- height: 152rpx;
- margin: 8rpx;
- border-radius: 16rpx;
- overflow: hidden;
- background-color: #f0f3f8;
- }
- .cp-img-cell--add {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- border: 2rpx dashed #d5dae6;
- background-color: #f7f9fc;
- }
- .cp-img-thumb {
- width: 100%;
- height: 100%;
- }
- .cp-img-remove {
- position: absolute;
- top: 0;
- right: 0;
- width: 40rpx;
- height: 40rpx;
- background: rgba(239, 68, 68, 0.92);
- display: flex;
- align-items: center;
- justify-content: center;
- border-bottom-left-radius: 12rpx;
- }
- .cp-img-remove-icon {
- color: #ffffff;
- font-size: 28rpx;
- line-height: 1;
- }
- .cp-img-add-icon {
- font-size: 44rpx;
- color: #4a8cff;
- line-height: 1;
- }
- .cp-img-add-txt {
- margin-top: 6rpx;
- font-size: 20rpx;
- color: #8a8d9e;
- }
- .cp-footer {
- position: fixed;
- left: 0;
- right: 0;
- bottom: 0;
- padding: 16rpx 28rpx 0;
- background: linear-gradient(180deg, rgba(244, 246, 251, 0) 0%, #f4f6fb 28%);
- box-sizing: border-box;
- }
- </style>
|