Commit 43eaaadb2858acee57c08cbc1d1d42574b0a0367

Authored by wuxw
1 parent 7505cb92

开发房屋优惠折扣

public/index.html
@@ -19,8 +19,6 @@ @@ -19,8 +19,6 @@
19 <script src="https://map.qq.com/api/gljs?v=1.exp&key="></script> 19 <script src="https://map.qq.com/api/gljs?v=1.exp&key="></script>
20 20
21 <!-- Qs 库 - 主 CDN --> 21 <!-- Qs 库 - 主 CDN -->
22 - <script src="https://cdn.bootcdn.net/ajax/libs/qs/6.10.3/qs.min.js"  
23 - onerror="handleScriptError('Qs-bootcdn', new Error('bootcdn 加载失败'))"></script>  
24 <script src="/js/jessibuca/jessibuca.js"></script> 22 <script src="/js/jessibuca/jessibuca.js"></script>
25 23
26 24
src/api/fee/applyRoomDiscountManageApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +// 查询优惠申请列表
  5 +export function queryApplyRoomDiscount(params) {
  6 + return new Promise((resolve, reject) => {
  7 + request({
  8 + url: '/applyRoomDiscount/queryApplyRoomDiscount',
  9 + method: 'get',
  10 + params: {
  11 + ...params,
  12 + communityId: getCommunityId()
  13 + }
  14 + }).then(response => {
  15 + const res = response.data
  16 + resolve(res)
  17 + }).catch(error => {
  18 + reject(error)
  19 + })
  20 + })
  21 +}
  22 +
  23 +
  24 +
  25 +// 查询优惠申请列表
  26 +export function listRoomsWhereFeeSet(params) {
  27 + return new Promise((resolve, reject) => {
  28 + request({
  29 + url: '/fee.listRoomsWhereFeeSet',
  30 + method: 'get',
  31 + params: {
  32 + ...params,
  33 + communityId: getCommunityId()
  34 + }
  35 + }).then(response => {
  36 + const res = response.data
  37 + resolve(res)
  38 + }).catch(error => {
  39 + reject(error)
  40 + })
  41 + })
  42 +}
  43 +
  44 +// 查询优惠申请类型
  45 +export function queryApplyRoomDiscountType(params) {
  46 + return new Promise((resolve, reject) => {
  47 + request({
  48 + url: '/applyRoomDiscount/queryApplyRoomDiscountType',
  49 + method: 'get',
  50 + params: {
  51 + ...params,
  52 + communityId: getCommunityId()
  53 + }
  54 + }).then(response => {
  55 + const res = response.data
  56 + resolve(res)
  57 + }).catch(error => {
  58 + reject(error)
  59 + })
  60 + })
  61 +}
  62 +
  63 +// 查询费用折扣
  64 +export function queryFeeDiscount(params) {
  65 + return new Promise((resolve, reject) => {
  66 + request({
  67 + url: '/feeDiscount/queryFeeDiscount',
  68 + method: 'get',
  69 + params: {
  70 + ...params,
  71 + communityId: getCommunityId()
  72 + }
  73 + }).then(response => {
  74 + const res = response.data
  75 + resolve(res)
  76 + }).catch(error => {
  77 + reject(error)
  78 + })
  79 + })
  80 +}
  81 +
  82 +// 保存优惠申请
  83 +export function saveApplyRoomDiscount(data) {
  84 + return new Promise((resolve, reject) => {
  85 + request({
  86 + url: '/applyRoomDiscount/saveApplyRoomDiscount',
  87 + method: 'post',
  88 + data: {
  89 + ...data,
  90 + communityId: getCommunityId()
  91 + }
  92 + }).then(response => {
  93 + const res = response.data
  94 + resolve(res)
  95 + }).catch(error => {
  96 + reject(error)
  97 + })
  98 + })
  99 +}
  100 +
  101 +// 更新优惠申请
  102 +export function updateApplyRoomDiscount(data) {
  103 + return new Promise((resolve, reject) => {
  104 + request({
  105 + url: '/applyRoomDiscount/updateApplyRoomDiscount',
  106 + method: 'post',
  107 + data: {
  108 + ...data,
  109 + communityId: getCommunityId()
  110 + }
  111 + }).then(response => {
  112 + const res = response.data
  113 + resolve(res)
  114 + }).catch(error => {
  115 + reject(error)
  116 + })
  117 + })
  118 +}
  119 +
  120 +// 更新审核优惠申请
  121 +export function updateReviewApplyRoomDiscount(data) {
  122 + return new Promise((resolve, reject) => {
  123 + request({
  124 + url: '/applyRoomDiscount/updateReviewApplyRoomDiscount',
  125 + method: 'post',
  126 + data: {
  127 + ...data,
  128 + communityId: getCommunityId()
  129 + }
  130 + }).then(response => {
  131 + const res = response.data
  132 + resolve(res)
  133 + }).catch(error => {
  134 + reject(error)
  135 + })
  136 + })
  137 +}
  138 +
  139 +// 删除优惠申请
  140 +export function deleteApplyRoomDiscount(data) {
  141 + return new Promise((resolve, reject) => {
  142 + request({
  143 + url: '/applyRoomDiscount/deleteApplyRoomDiscount',
  144 + method: 'post',
  145 + data: {
  146 + ...data,
  147 + communityId: getCommunityId()
  148 + }
  149 + }).then(response => {
  150 + const res = response.data
  151 + resolve(res)
  152 + }).catch(error => {
  153 + reject(error)
  154 + })
  155 + })
  156 +}
  157 +
  158 +// 处理返还金额计算
  159 +export function dealWithRefundAmount(data) {
  160 + return new Promise((resolve, reject) => {
  161 + request({
  162 + url: '/applyRoomDiscount/dealWithRefundAmount',
  163 + method: 'post',
  164 + data: {
  165 + ...data,
  166 + communityId: getCommunityId()
  167 + }
  168 + }).then(response => {
  169 + const res = response.data
  170 + resolve(res)
  171 + }).catch(error => {
  172 + reject(error)
  173 + })
  174 + })
  175 +}
0 \ No newline at end of file 176 \ No newline at end of file
src/api/fee/discountTypeApi.js 0 → 100644
  1 +import request from '@/utils/request'
  2 +import { getCommunityId } from '@/api/community/communityApi'
  3 +
  4 +/**
  5 + * 查询优惠类型列表
  6 + * @param {Object} params 查询参数
  7 + * @returns {Promise}
  8 + */
  9 +export function queryApplyRoomDiscountType(params) {
  10 + return new Promise((resolve, reject) => {
  11 + request({
  12 + url: '/applyRoomDiscount/queryApplyRoomDiscountType',
  13 + method: 'get',
  14 + params: {
  15 + ...params,
  16 + communityId: getCommunityId()
  17 + }
  18 + }).then(response => {
  19 + const res = response.data
  20 + resolve(res)
  21 + }).catch(error => {
  22 + reject(error)
  23 + })
  24 + })
  25 +}
  26 +
  27 +/**
  28 + * 添加优惠类型
  29 + * @param {Object} data 优惠类型数据
  30 + * @returns {Promise}
  31 + */
  32 +export function saveApplyRoomDiscountType(data) {
  33 + return new Promise((resolve, reject) => {
  34 + request({
  35 + url: '/applyRoomDiscount/saveApplyRoomDiscountType',
  36 + method: 'post',
  37 + data: {
  38 + ...data,
  39 + communityId: getCommunityId()
  40 + }
  41 + }).then(response => {
  42 + const res = response.data
  43 + resolve(res)
  44 + }).catch(error => {
  45 + reject(error)
  46 + })
  47 + })
  48 +}
  49 +
  50 +/**
  51 + * 更新优惠类型
  52 + * @param {Object} data 优惠类型数据
  53 + * @returns {Promise}
  54 + */
  55 +export function updateApplyRoomDiscountType(data) {
  56 + return new Promise((resolve, reject) => {
  57 + request({
  58 + url: '/applyRoomDiscount/updateApplyRoomDiscountType',
  59 + method: 'post',
  60 + data: {
  61 + ...data,
  62 + communityId: getCommunityId()
  63 + }
  64 + }).then(response => {
  65 + const res = response.data
  66 + resolve(res)
  67 + }).catch(error => {
  68 + reject(error)
  69 + })
  70 + })
  71 +}
  72 +
  73 +/**
  74 + * 删除优惠类型
  75 + * @param {Object} data 删除参数
  76 + * @returns {Promise}
  77 + */
  78 +export function deleteApplyRoomDiscountType(data) {
  79 + return new Promise((resolve, reject) => {
  80 + request({
  81 + url: '/applyRoomDiscount/deleteApplyRoomDiscountType',
  82 + method: 'post',
  83 + data: {
  84 + ...data,
  85 + communityId: getCommunityId()
  86 + }
  87 + }).then(response => {
  88 + const res = response.data
  89 + resolve(res)
  90 + }).catch(error => {
  91 + reject(error)
  92 + })
  93 + })
  94 +}
0 \ No newline at end of file 95 \ No newline at end of file
src/components/fee/addApplyRoomDiscount.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('applyRoomDiscount.add.title')" :visible.sync="visible" width="60%" @close="handleClose">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  4 + <el-form-item :label="$t('applyRoomDiscount.add.room')" prop="roomName">
  5 + <el-input v-model.trim="form.roomName" :placeholder="$t('applyRoomDiscount.add.roomPlaceholder')"
  6 + @blur="queryRoom" />
  7 + </el-form-item>
  8 +
  9 + <el-form-item :label="$t('applyRoomDiscount.add.applyType')" prop="applyType">
  10 + <el-select v-model="form.applyType" :placeholder="$t('applyRoomDiscount.add.applyTypePlaceholder')"
  11 + style="width:100%">
  12 + <el-option v-for="item in applyTypes" :key="item.applyType" :label="item.typeName" :value="item.applyType" />
  13 + </el-select>
  14 + </el-form-item>
  15 +
  16 + <el-form-item :label="$t('applyRoomDiscount.add.feeItem')" prop="feeId">
  17 + <el-select v-model="form.feeId" :placeholder="$t('applyRoomDiscount.add.feeItemPlaceholder')"
  18 + style="width:100%">
  19 + <el-option v-for="item in feeTypeCds" :key="item.feeId"
  20 + :label="`${item.feeName}(${item.endTime}至${item.deadlineTime})`" :value="item.feeId" />
  21 + </el-select>
  22 + </el-form-item>
  23 +
  24 + <el-form-item :label="$t('applyRoomDiscount.add.createUser')" prop="createUserName">
  25 + <el-input v-model.trim="form.createUserName" :placeholder="$t('applyRoomDiscount.add.createUserPlaceholder')"
  26 + disabled />
  27 + </el-form-item>
  28 +
  29 + <el-form-item :label="$t('applyRoomDiscount.add.createUserTel')" prop="createUserTel">
  30 + <el-input v-model.trim="form.createUserTel" :placeholder="$t('applyRoomDiscount.add.createUserTelPlaceholder')"
  31 + disabled />
  32 + </el-form-item>
  33 +
  34 + <el-form-item :label="$t('applyRoomDiscount.add.startTime')" prop="startTime">
  35 + <el-date-picker v-model="form.startTime" type="date"
  36 + :placeholder="$t('applyRoomDiscount.add.startTimePlaceholder')" value-format="yyyy-MM-dd" style="width:100%"
  37 + @change="validateDate" />
  38 + </el-form-item>
  39 +
  40 + <el-form-item :label="$t('applyRoomDiscount.add.endTime')" prop="endTime">
  41 + <el-date-picker v-model="form.endTime" type="date" :placeholder="$t('applyRoomDiscount.add.endTimePlaceholder')"
  42 + value-format="yyyy-MM-dd" style="width:100%" @change="validateDate" />
  43 + </el-form-item>
  44 +
  45 + <el-form-item :label="$t('applyRoomDiscount.add.createRemark')" prop="createRemark">
  46 + <el-input v-model="form.createRemark" type="textarea"
  47 + :placeholder="$t('applyRoomDiscount.add.createRemarkPlaceholder')" :rows="3" />
  48 + </el-form-item>
  49 +
  50 + <el-form-item :label="$t('applyRoomDiscount.add.images')">
  51 + <upload-image-url ref="uploadImage" :image-count="4" @change="handleImageChange" />
  52 + </el-form-item>
  53 + </el-form>
  54 +
  55 + <div slot="footer" class="dialog-footer">
  56 + <el-button @click="visible = false">
  57 + {{ $t('common.cancel') }}
  58 + </el-button>
  59 + <el-button type="primary" @click="submitForm">
  60 + {{ $t('common.save') }}
  61 + </el-button>
  62 + </div>
  63 + </el-dialog>
  64 +</template>
  65 +
  66 +<script>
  67 +import { queryApplyRoomDiscountType ,listRoomsWhereFeeSet} from '@/api/fee/applyRoomDiscountManageApi'
  68 +import UploadImageUrl from '@/components/upload/UploadImageUrl'
  69 +
  70 +export default {
  71 + name: 'AddApplyRoomDiscount',
  72 + components: {
  73 + UploadImageUrl
  74 + },
  75 + data() {
  76 + return {
  77 + visible: false,
  78 + form: {
  79 + roomName: '',
  80 + roomId: '',
  81 + applyType: '',
  82 + createUserName: '',
  83 + createUserTel: '',
  84 + startTime: '',
  85 + endTime: '',
  86 + createRemark: '',
  87 + feeId: '',
  88 + photos: [],
  89 + communityId: ''
  90 + },
  91 + applyTypes: [],
  92 + feeTypeCds: [],
  93 + rules: {
  94 + roomName: [
  95 + { required: true, message: this.$t('applyRoomDiscount.validate.roomRequired'), trigger: 'blur' },
  96 + { max: 64, message: this.$t('applyRoomDiscount.validate.roomFormat'), trigger: 'blur' }
  97 + ],
  98 + applyType: [
  99 + { required: true, message: this.$t('applyRoomDiscount.validate.applyTypeRequired'), trigger: 'change' }
  100 + ],
  101 + feeId: [
  102 + { required: true, message: this.$t('applyRoomDiscount.validate.feeIdRequired'), trigger: 'change' }
  103 + ],
  104 + createUserName: [
  105 + { required: true, message: this.$t('applyRoomDiscount.validate.createUserRequired'), trigger: 'blur' },
  106 + { max: 64, message: this.$t('applyRoomDiscount.validate.createUserFormat'), trigger: 'blur' }
  107 + ],
  108 + createUserTel: [
  109 + { required: true, message: this.$t('applyRoomDiscount.validate.createUserTelRequired'), trigger: 'blur' },
  110 + { pattern: /^1[3-9]\d{9}$/, message: this.$t('applyRoomDiscount.validate.createUserTelFormat'), trigger: 'blur' }
  111 + ],
  112 + startTime: [
  113 + { required: true, message: this.$t('applyRoomDiscount.validate.startTimeRequired'), trigger: 'change' }
  114 + ],
  115 + endTime: [
  116 + { required: true, message: this.$t('applyRoomDiscount.validate.endTimeRequired'), trigger: 'change' }
  117 + ],
  118 + createRemark: [
  119 + { required: true, message: this.$t('applyRoomDiscount.validate.createRemarkRequired'), trigger: 'blur' },
  120 + { max: 512, message: this.$t('applyRoomDiscount.validate.createRemarkFormat'), trigger: 'blur' }
  121 + ]
  122 + }
  123 + }
  124 + },
  125 + methods: {
  126 + open() {
  127 + this.visible = true
  128 + this.getApplyTypes()
  129 + },
  130 + handleClose() {
  131 + this.$refs.form.resetFields()
  132 + this.$refs.uploadImage.clear()
  133 + this.form = {
  134 + roomName: '',
  135 + roomId: '',
  136 + applyType: '',
  137 + createUserName: '',
  138 + createUserTel: '',
  139 + startTime: '',
  140 + endTime: '',
  141 + createRemark: '',
  142 + feeId: '',
  143 + photos: [],
  144 + communityId: ''
  145 + }
  146 + },
  147 + async getApplyTypes() {
  148 + try {
  149 + const { data } = await queryApplyRoomDiscountType({
  150 + page: 1,
  151 + row: 50,
  152 + communityId: this.form.communityId
  153 + })
  154 + this.applyTypes = data
  155 + } catch (error) {
  156 + console.error('获取申请类型失败:', error)
  157 + }
  158 + },
  159 + async queryRoom() {
  160 + if (!this.form.roomName) return
  161 +
  162 + const parts = this.form.roomName.split('-')
  163 + if (parts.length !== 3) {
  164 + this.$message.error(this.$t('applyRoomDiscount.validate.roomFormat'))
  165 + this.form.roomName = ''
  166 + return
  167 + }
  168 +
  169 + try {
  170 + const params = {
  171 + page: 1,
  172 + row: 1,
  173 + communityId: this.form.communityId,
  174 + flag: 0,
  175 + floorNum: parts[0].trim(),
  176 + unitNum: parts[1].trim(),
  177 + roomNum: parts[2].trim()
  178 + }
  179 +
  180 + const { rooms } = await listRoomsWhereFeeSet(params)
  181 + if (rooms.length === 0) {
  182 + this.$message.error(this.$t('applyRoomDiscount.validate.roomNotFound'))
  183 + this.form.roomName = ''
  184 + return
  185 + }
  186 +
  187 + const room = rooms[0]
  188 + this.form.roomId = room.roomId
  189 + this.form.createUserName = room.ownerName
  190 + this.form.createUserTel = room.link
  191 + this.queryRoomFees()
  192 + } catch (error) {
  193 + console.error('查询房屋失败:', error)
  194 + }
  195 + },
  196 + async queryRoomFees() {
  197 + try {
  198 + const params = {
  199 + page: 1,
  200 + row: 50,
  201 + communityId: this.form.communityId,
  202 + payerObjId: this.form.roomId,
  203 + state: '2008001'
  204 + }
  205 +
  206 + const { data } = await this.$api.fee.listFee(params)
  207 + this.feeTypeCds = data.fees
  208 + } catch (error) {
  209 + console.error('查询费用项失败:', error)
  210 + }
  211 + },
  212 + validateDate() {
  213 + if (this.form.startTime && this.form.endTime) {
  214 + const start = new Date(this.form.startTime)
  215 + const end = new Date(this.form.endTime)
  216 + if (start >= end) {
  217 + this.$message.error(this.$t('applyRoomDiscount.validate.dateInvalid'))
  218 + this.form.endTime = ''
  219 + }
  220 + }
  221 + },
  222 + handleImageChange(photos) {
  223 + this.form.photos = photos
  224 + },
  225 + submitForm() {
  226 + this.$refs.form.validate(valid => {
  227 + if (valid) {
  228 + this.saveApplyRoomDiscount()
  229 + }
  230 + })
  231 + },
  232 + async saveApplyRoomDiscount() {
  233 + try {
  234 + this.form.communityId = this.$store.getters.communityId
  235 + await this.$api.applyRoomDiscount.saveApplyRoomDiscount(this.form)
  236 + this.$message.success(this.$t('applyRoomDiscount.message.saveSuccess'))
  237 + this.visible = false
  238 + this.$emit('success')
  239 + } catch (error) {
  240 + this.$message.error(error.message || this.$t('applyRoomDiscount.message.saveFailed'))
  241 + }
  242 + }
  243 + }
  244 +}
  245 +</script>
  246 +
  247 +<style lang="scss" scoped>
  248 +.el-form-item {
  249 + margin-bottom: 22px;
  250 +}
  251 +</style>
0 \ No newline at end of file 252 \ No newline at end of file
src/components/fee/addApplyRoomDiscountType.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('discountType.add.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="formData"
  11 + :rules="rules"
  12 + label-width="120px"
  13 + >
  14 + <el-form-item
  15 + :label="$t('discountType.form.typeName')"
  16 + prop="typeName"
  17 + >
  18 + <el-input
  19 + v-model="formData.typeName"
  20 + :placeholder="$t('discountType.form.typeNamePlaceholder')"
  21 + />
  22 + </el-form-item>
  23 + <el-form-item
  24 + :label="$t('discountType.form.typeDesc')"
  25 + prop="typeDesc"
  26 + >
  27 + <el-input
  28 + v-model="formData.typeDesc"
  29 + type="textarea"
  30 + :placeholder="$t('discountType.form.typeDescPlaceholder')"
  31 + :rows="3"
  32 + />
  33 + </el-form-item>
  34 + </el-form>
  35 +
  36 + <div slot="footer" class="dialog-footer">
  37 + <el-button @click="visible = false">
  38 + {{ $t('common.cancel') }}
  39 + </el-button>
  40 + <el-button type="primary" @click="handleSubmit">
  41 + {{ $t('common.confirm') }}
  42 + </el-button>
  43 + </div>
  44 + </el-dialog>
  45 +</template>
  46 +
  47 +<script>
  48 +import { saveApplyRoomDiscountType } from '@/api/fee/discountTypeApi'
  49 +import { getCommunityId } from '@/api/community/communityApi'
  50 +
  51 +export default {
  52 + name: 'AddApplyRoomDiscountType',
  53 + data() {
  54 + return {
  55 + visible: false,
  56 + formData: {
  57 + typeName: '',
  58 + typeDesc: '',
  59 + communityId: ''
  60 + },
  61 + rules: {
  62 + typeName: [
  63 + { required: true, message: this.$t('discountType.validate.typeNameRequired'), trigger: 'blur' },
  64 + { max: 64, message: this.$t('discountType.validate.typeNameMaxLength'), trigger: 'blur' }
  65 + ],
  66 + typeDesc: [
  67 + { max: 512, message: this.$t('discountType.validate.typeDescMaxLength'), trigger: 'blur' }
  68 + ]
  69 + }
  70 + }
  71 + },
  72 + methods: {
  73 + open() {
  74 + this.visible = true
  75 + this.$nextTick(() => {
  76 + this.$refs.form && this.$refs.form.resetFields()
  77 + })
  78 + },
  79 + handleClose() {
  80 + this.$refs.form.resetFields()
  81 + },
  82 + handleSubmit() {
  83 + this.$refs.form.validate(async valid => {
  84 + if (valid) {
  85 + try {
  86 + this.formData.communityId = getCommunityId()
  87 + await saveApplyRoomDiscountType(this.formData)
  88 + this.$message.success(this.$t('discountType.add.success'))
  89 + this.visible = false
  90 + this.$emit('success')
  91 + } catch (error) {
  92 + console.error(error)
  93 + }
  94 + }
  95 + })
  96 + }
  97 + }
  98 +}
  99 +</script>
0 \ No newline at end of file 100 \ No newline at end of file
src/components/fee/deleteApplyRoomDiscount.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('applyRoomDiscount.delete.title')"
  4 + :visible.sync="visible"
  5 + width="500px"
  6 + @close="handleClose"
  7 + >
  8 + <div class="delete-content">
  9 + <p>{{ $t('applyRoomDiscount.delete.confirmText') }}</p>
  10 + </div>
  11 +
  12 + <div slot="footer" class="dialog-footer">
  13 + <el-button @click="visible = false">
  14 + {{ $t('common.cancel') }}
  15 + </el-button>
  16 + <el-button
  17 + type="primary"
  18 + @click="confirmDelete"
  19 + :loading="loading"
  20 + >
  21 + {{ $t('common.confirm') }}
  22 + </el-button>
  23 + </div>
  24 + </el-dialog>
  25 +</template>
  26 +
  27 +<script>
  28 +import { deleteApplyRoomDiscount } from '@/api/fee/applyRoomDiscountManageApi'
  29 +
  30 +export default {
  31 + name: 'DeleteApplyRoomDiscount',
  32 + data() {
  33 + return {
  34 + visible: false,
  35 + loading: false,
  36 + form: {
  37 + ardId: '',
  38 + communityId: ''
  39 + }
  40 + }
  41 + },
  42 + methods: {
  43 + open(data) {
  44 + this.form = {
  45 + ardId: data.ardId,
  46 + communityId: this.$store.getters.communityId
  47 + }
  48 + this.visible = true
  49 + },
  50 + handleClose() {
  51 + this.form = {
  52 + ardId: '',
  53 + communityId: ''
  54 + }
  55 + this.loading = false
  56 + },
  57 + async confirmDelete() {
  58 + try {
  59 + this.loading = true
  60 + await deleteApplyRoomDiscount(this.form)
  61 + this.$message.success(this.$t('applyRoomDiscount.message.deleteSuccess'))
  62 + this.visible = false
  63 + this.$emit('success')
  64 + } catch (error) {
  65 + this.$message.error(error.message || this.$t('applyRoomDiscount.message.deleteFailed'))
  66 + } finally {
  67 + this.loading = false
  68 + }
  69 + }
  70 + }
  71 +}
  72 +</script>
  73 +
  74 +<style lang="scss" scoped>
  75 +.delete-content {
  76 + text-align: center;
  77 + font-size: 16px;
  78 + padding: 20px 0;
  79 +}
  80 +
  81 +.dialog-footer {
  82 + text-align: right;
  83 +}
  84 +</style>
0 \ No newline at end of file 85 \ No newline at end of file
src/components/fee/deleteApplyRoomDiscountType.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('discountType.delete.title')"
  4 + :visible.sync="visible"
  5 + width="30%"
  6 + @close="handleClose"
  7 + >
  8 + <div class="delete-content">
  9 + <p>{{ $t('discountType.delete.confirmText') }}</p>
  10 + <p class="delete-item">
  11 + <span class="label">{{ $t('discountType.table.applyType') }}:</span>
  12 + <span>{{ formData.applyType }}</span>
  13 + </p>
  14 + <p class="delete-item">
  15 + <span class="label">{{ $t('discountType.table.typeName') }}:</span>
  16 + <span>{{ formData.typeName }}</span>
  17 + </p>
  18 + </div>
  19 +
  20 + <div slot="footer" class="dialog-footer">
  21 + <el-button @click="visible = false">
  22 + {{ $t('common.cancel') }}
  23 + </el-button>
  24 + <el-button type="danger" @click="handleConfirm">
  25 + {{ $t('common.confirm') }}
  26 + </el-button>
  27 + </div>
  28 + </el-dialog>
  29 +</template>
  30 +
  31 +<script>
  32 +import { deleteApplyRoomDiscountType } from '@/api/fee/discountTypeApi'
  33 +import { getCommunityId } from '@/api/community/communityApi'
  34 +
  35 +export default {
  36 + name: 'DeleteApplyRoomDiscountType',
  37 + data() {
  38 + return {
  39 + visible: false,
  40 + formData: {
  41 + applyType: '',
  42 + typeName: '',
  43 + communityId: ''
  44 + }
  45 + }
  46 + },
  47 + methods: {
  48 + open(row) {
  49 + this.visible = true
  50 + this.formData = { ...row }
  51 + },
  52 + handleClose() {
  53 + this.formData = {
  54 + applyType: '',
  55 + typeName: '',
  56 + communityId: ''
  57 + }
  58 + },
  59 + async handleConfirm() {
  60 + try {
  61 + this.formData.communityId = getCommunityId()
  62 + await deleteApplyRoomDiscountType(this.formData)
  63 + this.$message.success(this.$t('discountType.delete.success'))
  64 + this.visible = false
  65 + this.$emit('success')
  66 + } catch (error) {
  67 + console.error(error)
  68 + }
  69 + }
  70 + }
  71 +}
  72 +</script>
  73 +
  74 +<style scoped>
  75 +.delete-content {
  76 + padding: 0 20px;
  77 +}
  78 +.delete-item {
  79 + margin: 10px 0;
  80 +}
  81 +.label {
  82 + font-weight: bold;
  83 + margin-right: 10px;
  84 +}
  85 +</style>
0 \ No newline at end of file 86 \ No newline at end of file
src/components/fee/editApplyRoomDiscount.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('applyRoomDiscount.edit.title')" :visible.sync="visible" width="60%" @close="handleClose">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="120px">
  4 + <el-form-item :label="$t('applyRoomDiscount.edit.startTime')" prop="startTime">
  5 + <el-date-picker v-model="form.startTime" type="datetime"
  6 + :placeholder="$t('applyRoomDiscount.edit.startTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss"
  7 + style="width:100%" @change="validateDate" />
  8 + </el-form-item>
  9 +
  10 + <el-form-item :label="$t('applyRoomDiscount.edit.endTime')" prop="endTime">
  11 + <el-date-picker v-model="form.endTime" type="datetime"
  12 + :placeholder="$t('applyRoomDiscount.edit.endTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss"
  13 + style="width:100%" @change="validateDate" />
  14 + </el-form-item>
  15 +
  16 + <el-form-item :label="$t('applyRoomDiscount.edit.state')" prop="state">
  17 + <el-select v-model="form.state" :placeholder="$t('applyRoomDiscount.edit.statePlaceholder')" style="width:100%">
  18 + <el-option :label="$t('applyRoomDiscount.status.checkPass')" value="2" />
  19 + <el-option :label="$t('applyRoomDiscount.status.checkReject')" value="3" />
  20 + </el-select>
  21 + </el-form-item>
  22 +
  23 + <el-form-item :label="$t('applyRoomDiscount.edit.createRemark')">
  24 + <el-input v-model="form.createRemark" type="textarea" :rows="3" disabled />
  25 + </el-form-item>
  26 +
  27 + <el-form-item :label="$t('applyRoomDiscount.edit.checkRemark')" prop="checkRemark">
  28 + <el-input v-model="form.checkRemark" type="textarea"
  29 + :placeholder="$t('applyRoomDiscount.edit.checkRemarkPlaceholder')" :rows="3" />
  30 + </el-form-item>
  31 +
  32 + <el-form-item :label="$t('applyRoomDiscount.edit.images')">
  33 + <upload-image-url ref="uploadImage" :image-count="4" @change="handleImageChange" />
  34 + </el-form-item>
  35 + </el-form>
  36 +
  37 + <div slot="footer" class="dialog-footer">
  38 + <el-button @click="visible = false">
  39 + {{ $t('common.cancel') }}
  40 + </el-button>
  41 + <el-button type="primary" @click="submitForm">
  42 + {{ $t('common.save') }}
  43 + </el-button>
  44 + </div>
  45 + </el-dialog>
  46 +</template>
  47 +
  48 +<script>
  49 +import { updateApplyRoomDiscount } from '@/api/fee/applyRoomDiscountManageApi'
  50 +import UploadImageUrl from '@/components/upload/UploadImageUrl'
  51 +
  52 +export default {
  53 + name: 'EditApplyRoomDiscount',
  54 + components: {
  55 + UploadImageUrl
  56 + },
  57 + data() {
  58 + return {
  59 + visible: false,
  60 + form: {
  61 + ardId: '',
  62 + startTime: '',
  63 + endTime: '',
  64 + checkRemark: '',
  65 + createRemark: '',
  66 + state: '',
  67 + photos: [],
  68 + urls: [],
  69 + communityId: ''
  70 + },
  71 + rules: {
  72 + startTime: [
  73 + { required: true, message: this.$t('applyRoomDiscount.validate.startTimeRequired'), trigger: 'change' }
  74 + ],
  75 + endTime: [
  76 + { required: true, message: this.$t('applyRoomDiscount.validate.endTimeRequired'), trigger: 'change' }
  77 + ],
  78 + state: [
  79 + { required: true, message: this.$t('applyRoomDiscount.validate.stateRequired'), trigger: 'change' }
  80 + ],
  81 + checkRemark: [
  82 + { required: true, message: this.$t('applyRoomDiscount.validate.checkRemarkRequired'), trigger: 'blur' },
  83 + { max: 512, message: this.$t('applyRoomDiscount.validate.checkRemarkFormat'), trigger: 'blur' }
  84 + ]
  85 + }
  86 + }
  87 + },
  88 + methods: {
  89 + open(data) {
  90 + this.form = {
  91 + ...this.form,
  92 + ...data,
  93 + communityId: this.$store.getters.communityId
  94 + }
  95 + this.$nextTick(() => {
  96 + if (this.$refs.uploadImage) {
  97 + this.$refs.uploadImage.setImages(this.form.urls || [])
  98 + }
  99 + })
  100 + this.visible = true
  101 + },
  102 + handleClose() {
  103 + this.$refs.form.resetFields()
  104 + if (this.$refs.uploadImage) {
  105 + this.$refs.uploadImage.clear()
  106 + }
  107 + this.form = {
  108 + ardId: '',
  109 + startTime: '',
  110 + endTime: '',
  111 + checkRemark: '',
  112 + createRemark: '',
  113 + state: '',
  114 + photos: [],
  115 + urls: [],
  116 + communityId: ''
  117 + }
  118 + },
  119 + validateDate() {
  120 + if (this.form.startTime && this.form.endTime) {
  121 + const start = new Date(this.form.startTime)
  122 + const end = new Date(this.form.endTime)
  123 + if (start >= end) {
  124 + this.$message.error(this.$t('applyRoomDiscount.validate.dateInvalid'))
  125 + this.form.endTime = ''
  126 + }
  127 + }
  128 + },
  129 + handleImageChange(photos) {
  130 + this.form.photos = photos
  131 + },
  132 + submitForm() {
  133 + this.$refs.form.validate(valid => {
  134 + if (valid) {
  135 + this.updateApplyRoomDiscount()
  136 + }
  137 + })
  138 + },
  139 + async updateApplyRoomDiscount() {
  140 + try {
  141 + await updateApplyRoomDiscount(this.form)
  142 + this.$message.success(this.$t('applyRoomDiscount.message.updateSuccess'))
  143 + this.visible = false
  144 + this.$emit('success')
  145 + } catch (error) {
  146 + this.$message.error(error.message || this.$t('applyRoomDiscount.message.updateFailed'))
  147 + }
  148 + }
  149 + }
  150 +}
  151 +</script>
  152 +
  153 +<style lang="scss" scoped>
  154 +.el-form-item {
  155 + margin-bottom: 22px;
  156 +}
  157 +</style>
0 \ No newline at end of file 158 \ No newline at end of file
src/components/fee/editApplyRoomDiscountRecord.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('applyRoomDiscount.editRecord.title')"
  4 + :visible.sync="visible"
  5 + width="60%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="form"
  11 + :rules="rules"
  12 + label-width="120px"
  13 + >
  14 + <el-form-item
  15 + :label="$t('applyRoomDiscount.editRecord.ardId')"
  16 + prop="ardId"
  17 + >
  18 + <el-input
  19 + v-model="form.ardId"
  20 + disabled
  21 + />
  22 + </el-form-item>
  23 +
  24 + <el-form-item
  25 + :label="$t('applyRoomDiscount.editRecord.roomName')"
  26 + prop="roomName"
  27 + >
  28 + <el-input
  29 + v-model="form.roomName"
  30 + disabled
  31 + />
  32 + </el-form-item>
  33 +
  34 + <el-form-item
  35 + :label="$t('applyRoomDiscount.editRecord.applyTypeName')"
  36 + prop="applyTypeName"
  37 + >
  38 + <el-input
  39 + v-model="form.applyTypeName"
  40 + disabled
  41 + />
  42 + </el-form-item>
  43 +
  44 + <el-form-item
  45 + :label="$t('applyRoomDiscount.editRecord.createUserName')"
  46 + prop="createUserName"
  47 + >
  48 + <el-input
  49 + v-model="form.createUserName"
  50 + disabled
  51 + />
  52 + </el-form-item>
  53 +
  54 + <el-form-item
  55 + :label="$t('applyRoomDiscount.editRecord.createUserTel')"
  56 + prop="createUserTel"
  57 + >
  58 + <el-input
  59 + v-model="form.createUserTel"
  60 + disabled
  61 + />
  62 + </el-form-item>
  63 +
  64 + <el-form-item
  65 + :label="$t('applyRoomDiscount.editRecord.startTime')"
  66 + prop="startTime"
  67 + >
  68 + <el-date-picker
  69 + v-model="form.startTime"
  70 + type="datetime"
  71 + :placeholder="$t('applyRoomDiscount.editRecord.startTimePlaceholder')"
  72 + value-format="yyyy-MM-dd HH:mm:ss"
  73 + style="width:100%"
  74 + @change="validateDate"
  75 + />
  76 + </el-form-item>
  77 +
  78 + <el-form-item
  79 + :label="$t('applyRoomDiscount.editRecord.endTime')"
  80 + prop="endTime"
  81 + >
  82 + <el-date-picker
  83 + v-model="form.endTime"
  84 + type="datetime"
  85 + :placeholder="$t('applyRoomDiscount.editRecord.endTimePlaceholder')"
  86 + value-format="yyyy-MM-dd HH:mm:ss"
  87 + style="width:100%"
  88 + @change="validateDate"
  89 + />
  90 + </el-form-item>
  91 +
  92 + <el-form-item
  93 + :label="$t('applyRoomDiscount.editRecord.stateName')"
  94 + prop="stateName"
  95 + >
  96 + <el-input
  97 + v-model="form.stateName"
  98 + disabled
  99 + />
  100 + </el-form-item>
  101 + </el-form>
  102 +
  103 + <div slot="footer" class="dialog-footer">
  104 + <el-button @click="visible = false">
  105 + {{ $t('common.cancel') }}
  106 + </el-button>
  107 + <el-button type="primary" @click="submitForm">
  108 + {{ $t('common.save') }}
  109 + </el-button>
  110 + </div>
  111 + </el-dialog>
  112 +</template>
  113 +
  114 +<script>
  115 +import { updateApplyRoomDiscount } from '@/api/fee/applyRoomDiscountManageApi'
  116 +
  117 +export default {
  118 + name: 'EditApplyRoomDiscountRecord',
  119 + data() {
  120 + return {
  121 + visible: false,
  122 + form: {
  123 + ardId: '',
  124 + roomName: '',
  125 + roomId: '',
  126 + discountId: '',
  127 + discountName: '',
  128 + applyTypeName: '',
  129 + createUserName: '',
  130 + createUserTel: '',
  131 + startTime: '',
  132 + endTime: '',
  133 + stateName: '',
  134 + state: '',
  135 + communityId: ''
  136 + },
  137 + rules: {
  138 + ardId: [
  139 + { required: true, message: this.$t('applyRoomDiscount.validate.ardIdRequired'), trigger: 'blur' }
  140 + ],
  141 + roomName: [
  142 + { required: true, message: this.$t('applyRoomDiscount.validate.roomRequired'), trigger: 'blur' }
  143 + ],
  144 + applyTypeName: [
  145 + { required: true, message: this.$t('applyRoomDiscount.validate.applyTypeRequired'), trigger: 'blur' }
  146 + ],
  147 + createUserName: [
  148 + { required: true, message: this.$t('applyRoomDiscount.validate.createUserRequired'), trigger: 'blur' }
  149 + ],
  150 + startTime: [
  151 + { required: true, message: this.$t('applyRoomDiscount.validate.startTimeRequired'), trigger: 'change' }
  152 + ],
  153 + endTime: [
  154 + { required: true, message: this.$t('applyRoomDiscount.validate.endTimeRequired'), trigger: 'change' }
  155 + ],
  156 + stateName: [
  157 + { required: true, message: this.$t('applyRoomDiscount.validate.stateRequired'), trigger: 'blur' }
  158 + ]
  159 + }
  160 + }
  161 + },
  162 + methods: {
  163 + open(data) {
  164 + this.form = {
  165 + ...this.form,
  166 + ...data,
  167 + communityId: this.$store.getters.communityId
  168 + }
  169 + this.visible = true
  170 + },
  171 + handleClose() {
  172 + this.$refs.form.resetFields()
  173 + this.form = {
  174 + ardId: '',
  175 + roomName: '',
  176 + roomId: '',
  177 + discountId: '',
  178 + discountName: '',
  179 + applyTypeName: '',
  180 + createUserName: '',
  181 + createUserTel: '',
  182 + startTime: '',
  183 + endTime: '',
  184 + stateName: '',
  185 + state: '',
  186 + communityId: ''
  187 + }
  188 + },
  189 + validateDate() {
  190 + if (this.form.startTime && this.form.endTime) {
  191 + const start = new Date(this.form.startTime)
  192 + const end = new Date(this.form.endTime)
  193 + if (start >= end) {
  194 + this.$message.error(this.$t('applyRoomDiscount.validate.dateInvalid'))
  195 + this.form.endTime = ''
  196 + }
  197 + }
  198 + },
  199 + submitForm() {
  200 + this.$refs.form.validate(valid => {
  201 + if (valid) {
  202 + this.updateApplyRoomDiscount()
  203 + }
  204 + })
  205 + },
  206 + async updateApplyRoomDiscount() {
  207 + try {
  208 + await updateApplyRoomDiscount(this.form)
  209 + this.$message.success(this.$t('applyRoomDiscount.message.updateSuccess'))
  210 + this.visible = false
  211 + this.$emit('success')
  212 + } catch (error) {
  213 + this.$message.error(error.message || this.$t('applyRoomDiscount.message.updateFailed'))
  214 + }
  215 + }
  216 + }
  217 +}
  218 +</script>
  219 +
  220 +<style lang="scss" scoped>
  221 +.el-form-item {
  222 + margin-bottom: 22px;
  223 +}
  224 +</style>
0 \ No newline at end of file 225 \ No newline at end of file
src/components/fee/editApplyRoomDiscountType.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="$t('discountType.edit.title')"
  4 + :visible.sync="visible"
  5 + width="50%"
  6 + @close="handleClose"
  7 + >
  8 + <el-form
  9 + ref="form"
  10 + :model="formData"
  11 + :rules="rules"
  12 + label-width="120px"
  13 + >
  14 + <el-form-item
  15 + :label="$t('discountType.form.applyType')"
  16 + prop="applyType"
  17 + >
  18 + <el-input
  19 + v-model="formData.applyType"
  20 + disabled
  21 + />
  22 + </el-form-item>
  23 + <el-form-item
  24 + :label="$t('discountType.form.typeName')"
  25 + prop="typeName"
  26 + >
  27 + <el-input
  28 + v-model="formData.typeName"
  29 + :placeholder="$t('discountType.form.typeNamePlaceholder')"
  30 + />
  31 + </el-form-item>
  32 + <el-form-item
  33 + :label="$t('discountType.form.typeDesc')"
  34 + prop="typeDesc"
  35 + >
  36 + <el-input
  37 + v-model="formData.typeDesc"
  38 + type="textarea"
  39 + :placeholder="$t('discountType.form.typeDescPlaceholder')"
  40 + :rows="3"
  41 + />
  42 + </el-form-item>
  43 + </el-form>
  44 +
  45 + <div slot="footer" class="dialog-footer">
  46 + <el-button @click="visible = false">
  47 + {{ $t('common.cancel') }}
  48 + </el-button>
  49 + <el-button type="primary" @click="handleSubmit">
  50 + {{ $t('common.confirm') }}
  51 + </el-button>
  52 + </div>
  53 + </el-dialog>
  54 +</template>
  55 +
  56 +<script>
  57 +import { updateApplyRoomDiscountType } from '@/api/fee/discountTypeApi'
  58 +import { getCommunityId } from '@/api/community/communityApi'
  59 +
  60 +export default {
  61 + name: 'EditApplyRoomDiscountType',
  62 + data() {
  63 + return {
  64 + visible: false,
  65 + formData: {
  66 + applyType: '',
  67 + typeName: '',
  68 + typeDesc: '',
  69 + communityId: ''
  70 + },
  71 + rules: {
  72 + applyType: [
  73 + { required: true, message: this.$t('discountType.validate.applyTypeRequired'), trigger: 'blur' }
  74 + ],
  75 + typeName: [
  76 + { required: true, message: this.$t('discountType.validate.typeNameRequired'), trigger: 'blur' },
  77 + { max: 64, message: this.$t('discountType.validate.typeNameMaxLength'), trigger: 'blur' }
  78 + ],
  79 + typeDesc: [
  80 + { max: 512, message: this.$t('discountType.validate.typeDescMaxLength'), trigger: 'blur' }
  81 + ]
  82 + }
  83 + }
  84 + },
  85 + methods: {
  86 + open(row) {
  87 + this.visible = true
  88 + this.$nextTick(() => {
  89 + this.formData = { ...row }
  90 + })
  91 + },
  92 + handleClose() {
  93 + this.$refs.form.resetFields()
  94 + },
  95 + handleSubmit() {
  96 + this.$refs.form.validate(async valid => {
  97 + if (valid) {
  98 + try {
  99 + this.formData.communityId = getCommunityId()
  100 + await updateApplyRoomDiscountType(this.formData)
  101 + this.$message.success(this.$t('discountType.edit.success'))
  102 + this.visible = false
  103 + this.$emit('success')
  104 + } catch (error) {
  105 + console.error(error)
  106 + }
  107 + }
  108 + })
  109 + }
  110 + }
  111 +}
  112 +</script>
0 \ No newline at end of file 113 \ No newline at end of file
src/components/fee/reviewApplyRoomDiscount.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="$t('applyRoomDiscount.review.title')" :visible.sync="visible" width="70%" @close="handleClose">
  3 + <el-form ref="form" :model="form" :rules="rules" label-width="150px">
  4 + <template v-if="form.state !== '5'">
  5 + <el-form-item :label="$t('applyRoomDiscount.review.discountType')" prop="discountType">
  6 + <el-select v-model="form.discountType" :placeholder="$t('applyRoomDiscount.review.discountTypePlaceholder')"
  7 + style="width:100%" @change="handleDiscountTypeChange">
  8 + <el-option :label="$t('applyRoomDiscount.discountType.discount')" value="3003" />
  9 + </el-select>
  10 + </el-form-item>
  11 +
  12 + <el-form-item :label="$t('applyRoomDiscount.review.discountName')" prop="discountId">
  13 + <el-select v-model="form.discountId" :placeholder="$t('applyRoomDiscount.review.discountNamePlaceholder')"
  14 + style="width:100%">
  15 + <el-option v-for="item in discounts" :key="item.discountId" :label="item.discountName"
  16 + :value="item.discountId" />
  17 + </el-select>
  18 + </el-form-item>
  19 +
  20 + <el-form-item :label="$t('applyRoomDiscount.review.returnWay')" prop="returnWay">
  21 + <el-select v-model="form.returnWay" :placeholder="$t('applyRoomDiscount.review.returnWayPlaceholder')"
  22 + style="width:100%">
  23 + <el-option :label="$t('applyRoomDiscount.returnWay.discount')" value="1001" />
  24 + <el-option :label="$t('applyRoomDiscount.returnWay.account')" value="1002" />
  25 + </el-select>
  26 + </el-form-item>
  27 +
  28 + <div v-show="form.returnWay === '1002'">
  29 + <el-form-item :label="$t('applyRoomDiscount.review.paymentRecords')">
  30 + <div class="checkbox-group">
  31 + <el-checkbox v-for="item in fees" :key="item.detailId" v-model="form.selectedFees" :label="item.detailId"
  32 + @change="handleFeeSelection(item)">
  33 + 【{{ item.feeName }}】<span class="text-danger">{{ item.receivedAmount }}{{ $t('common.yuan') }}</span> -
  34 + {{ item.createTime }}
  35 + </el-checkbox>
  36 + </div>
  37 + </el-form-item>
  38 +
  39 + <el-form-item :label="$t('applyRoomDiscount.review.refundAmount')" prop="refundAmount">
  40 + <el-input v-model="form.refundAmount"
  41 + :placeholder="$t('applyRoomDiscount.review.refundAmountPlaceholder')" />
  42 + </el-form-item>
  43 + </div>
  44 + </template>
  45 +
  46 + <el-form-item :label="$t('applyRoomDiscount.review.startTime')" prop="startTime">
  47 + <el-date-picker v-model="form.startTime" type="datetime"
  48 + :placeholder="$t('applyRoomDiscount.review.startTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss"
  49 + style="width:100%" />
  50 + </el-form-item>
  51 +
  52 + <el-form-item :label="$t('applyRoomDiscount.review.endTime')" prop="endTime">
  53 + <el-date-picker v-model="form.endTime" type="datetime"
  54 + :placeholder="$t('applyRoomDiscount.review.endTimePlaceholder')" value-format="yyyy-MM-dd HH:mm:ss"
  55 + style="width:100%" />
  56 + </el-form-item>
  57 +
  58 + <el-form-item :label="$t('applyRoomDiscount.review.state')" prop="state">
  59 + <el-select v-model="form.state" :placeholder="$t('applyRoomDiscount.review.statePlaceholder')"
  60 + style="width:100%">
  61 + <el-option :label="$t('applyRoomDiscount.status.reviewPass')" value="4" />
  62 + <el-option :label="$t('applyRoomDiscount.status.reviewReject')" value="5" />
  63 + </el-select>
  64 + </el-form-item>
  65 +
  66 + <el-form-item :label="$t('applyRoomDiscount.review.createRemark')">
  67 + <el-input v-model="form.createRemark" type="textarea" :rows="2" disabled />
  68 + </el-form-item>
  69 +
  70 + <el-form-item :label="$t('applyRoomDiscount.review.checkRemark')">
  71 + <el-input v-model="form.checkRemark" type="textarea" :rows="2" disabled />
  72 + </el-form-item>
  73 +
  74 + <el-form-item :label="$t('applyRoomDiscount.review.reviewRemark')" prop="reviewRemark">
  75 + <el-input v-model="form.reviewRemark" type="textarea"
  76 + :placeholder="$t('applyRoomDiscount.review.reviewRemarkPlaceholder')" :rows="3" />
  77 + </el-form-item>
  78 +
  79 + <el-form-item :label="$t('applyRoomDiscount.review.images')">
  80 + <div class="image-preview-group">
  81 + <div v-for="(url, index) in form.images" :key="index" class="image-preview-item">
  82 + <img :src="getImageUrl(url)" @click="showImage(url)" />
  83 + </div>
  84 + </div>
  85 + </el-form-item>
  86 + </el-form>
  87 +
  88 + <div slot="footer" class="dialog-footer">
  89 + <el-button @click="visible = false">
  90 + {{ $t('common.cancel') }}
  91 + </el-button>
  92 + <el-button type="primary" @click="submitForm">
  93 + {{ $t('common.save') }}
  94 + </el-button>
  95 + </div>
  96 +
  97 + <view-image ref="viewImage" />
  98 + </el-dialog>
  99 +</template>
  100 +
  101 +<script>
  102 +//import { getDict } from '@/api/community/communityApi'
  103 +import { queryFeeDiscount, updateReviewApplyRoomDiscount } from '@/api/fee/applyRoomDiscountManageApi'
  104 +import ViewImage from '@/components/system/viewImage'
  105 +
  106 +export default {
  107 + name: 'ReviewApplyRoomDiscount',
  108 + components: {
  109 + ViewImage
  110 + },
  111 + data() {
  112 + return {
  113 + visible: false,
  114 + form: {
  115 + ardId: '',
  116 + discountId: '',
  117 + discountType: '',
  118 + startTime: '',
  119 + endTime: '',
  120 + createRemark: '',
  121 + checkRemark: '',
  122 + reviewRemark: '',
  123 + refundAmount: '',
  124 + state: '',
  125 + roomId: '',
  126 + discounts: [],
  127 + fees: [],
  128 + selectedFees: [],
  129 + images: [],
  130 + returnWay: '1001',
  131 + feeId: ''
  132 + },
  133 + discounts: [],
  134 + fees: [],
  135 + rules: {
  136 + discountId: [
  137 + { required: true, message: this.$t('applyRoomDiscount.validate.discountIdRequired'), trigger: 'change' }
  138 + ],
  139 + discountType: [
  140 + { required: true, message: this.$t('applyRoomDiscount.validate.discountTypeRequired'), trigger: 'change' }
  141 + ],
  142 + returnWay: [
  143 + { required: true, message: this.$t('applyRoomDiscount.validate.returnWayRequired'), trigger: 'change' }
  144 + ],
  145 + refundAmount: [
  146 + {
  147 + required: true,
  148 + message: this.$t('applyRoomDiscount.validate.refundAmountRequired'),
  149 + trigger: 'blur',
  150 + validator: (rule, value, callback) => {
  151 + if (this.form.returnWay === '1002' && !value) {
  152 + callback(new Error(this.$t('applyRoomDiscount.validate.refundAmountRequired')))
  153 + } else {
  154 + callback()
  155 + }
  156 + }
  157 + },
  158 + {
  159 + pattern: /^\d+(\.\d{1,2})?$/,
  160 + message: this.$t('applyRoomDiscount.validate.refundAmountFormat'),
  161 + trigger: 'blur'
  162 + }
  163 + ],
  164 + startTime: [
  165 + { required: true, message: this.$t('applyRoomDiscount.validate.startTimeRequired'), trigger: 'change' }
  166 + ],
  167 + endTime: [
  168 + { required: true, message: this.$t('applyRoomDiscount.validate.endTimeRequired'), trigger: 'change' }
  169 + ],
  170 + state: [
  171 + { required: true, message: this.$t('applyRoomDiscount.validate.stateRequired'), trigger: 'change' }
  172 + ],
  173 + reviewRemark: [
  174 + { required: true, message: this.$t('applyRoomDiscount.validate.reviewRemarkRequired'), trigger: 'blur' },
  175 + { max: 512, message: this.$t('applyRoomDiscount.validate.reviewRemarkFormat'), trigger: 'blur' }
  176 + ]
  177 + }
  178 + }
  179 + },
  180 + methods: {
  181 + open(data) {
  182 + this.form = {
  183 + ...this.form,
  184 + ...data,
  185 + communityId: this.$store.getters.communityId,
  186 + selectedFees: [],
  187 + refundAmount: ''
  188 + }
  189 + this.visible = true
  190 + this.loadDiscounts()
  191 + this.loadFees()
  192 + },
  193 + handleClose() {
  194 + this.$refs.form.resetFields()
  195 + this.form = {
  196 + ardId: '',
  197 + discountId: '',
  198 + discountType: '',
  199 + startTime: '',
  200 + endTime: '',
  201 + createRemark: '',
  202 + checkRemark: '',
  203 + reviewRemark: '',
  204 + refundAmount: '',
  205 + state: '',
  206 + roomId: '',
  207 + discounts: [],
  208 + fees: [],
  209 + selectedFees: [],
  210 + images: [],
  211 + returnWay: '1001',
  212 + feeId: ''
  213 + }
  214 + },
  215 + async loadDiscounts() {
  216 + try {
  217 + const { data } = await queryFeeDiscount({
  218 + page: 1,
  219 + row: 100,
  220 + communityId: this.form.communityId,
  221 + discountType: this.form.discountType || '3003'
  222 + })
  223 + this.discounts = data
  224 + } catch (error) {
  225 + console.error('加载折扣信息失败:', error)
  226 + }
  227 + },
  228 + async loadFees() {
  229 + try {
  230 + const { data } = await this.$api.fee.queryFeeDetail({
  231 + page: 1,
  232 + row: 50,
  233 + communityId: this.form.communityId,
  234 + feeId: this.form.feeId,
  235 + state: '1400'
  236 + })
  237 + this.fees = data.feeDetails
  238 + } catch (error) {
  239 + console.error('加载缴费记录失败:', error)
  240 + }
  241 + },
  242 + handleDiscountTypeChange() {
  243 + this.form.discountId = ''
  244 + this.loadDiscounts()
  245 + },
  246 + handleFeeSelection(item) {
  247 + console.log(item)
  248 + this.calculateRefundAmount()
  249 + },
  250 + calculateRefundAmount() {
  251 + if (this.form.returnWay !== '1002') return
  252 +
  253 + let total = 0
  254 + this.fees.forEach(item => {
  255 + if (this.form.selectedFees.includes(item.detailId)) {
  256 + total += parseFloat(item.receivedAmount) || 0
  257 + }
  258 + })
  259 + this.form.refundAmount = total.toFixed(2)
  260 + },
  261 + getImageUrl(url) {
  262 + if (url.startsWith('http') || url.startsWith('https') || url.startsWith('data:')) {
  263 + return url
  264 + }
  265 + return `/callComponent/download/getFile/file?fileId=${url}&communityId=-1&time=${new Date().getTime()}`
  266 + },
  267 + showImage(url) {
  268 + this.$refs.viewImage.open(this.getImageUrl(url))
  269 + },
  270 + submitForm() {
  271 + this.$refs.form.validate(valid => {
  272 + if (valid) {
  273 + this.updateReview()
  274 + }
  275 + })
  276 + },
  277 + async updateReview() {
  278 + try {
  279 + await updateReviewApplyRoomDiscount(this.form)
  280 + this.$message.success(this.$t('applyRoomDiscount.message.reviewSuccess'))
  281 + this.visible = false
  282 + this.$emit('success')
  283 + } catch (error) {
  284 + this.$message.error(error.message || this.$t('applyRoomDiscount.message.reviewFailed'))
  285 + }
  286 + }
  287 + }
  288 +}
  289 +</script>
  290 +
  291 +<style lang="scss" scoped>
  292 +.el-form-item {
  293 + margin-bottom: 22px;
  294 +}
  295 +
  296 +.checkbox-group {
  297 + max-height: 200px;
  298 + overflow-y: auto;
  299 + border: 1px solid #dcdfe6;
  300 + border-radius: 4px;
  301 + padding: 10px;
  302 +
  303 + .el-checkbox {
  304 + display: block;
  305 + margin: 5px 0;
  306 + }
  307 +}
  308 +
  309 +.text-danger {
  310 + color: #f56c6c;
  311 +}
  312 +
  313 +.image-preview-group {
  314 + display: flex;
  315 + flex-wrap: wrap;
  316 + gap: 10px;
  317 +
  318 + .image-preview-item {
  319 + width: 60px;
  320 + height: 60px;
  321 + border: 1px solid #dcdfe6;
  322 + border-radius: 4px;
  323 + overflow: hidden;
  324 + cursor: pointer;
  325 +
  326 + img {
  327 + width: 100%;
  328 + height: 100%;
  329 + object-fit: cover;
  330 + }
  331 + }
  332 +}
  333 +</style>
0 \ No newline at end of file 334 \ No newline at end of file
src/components/fee/uploadImageUrl.vue 0 → 100644
  1 +<template>
  2 + <div class="upload-image-container">
  3 + <div v-for="(image, index) in photos" :key="index" class="image-item">
  4 + <img :src="getImageUrl(image)" @error="handleImageError" />
  5 + <span class="remove-icon" @click="removeImage(index)">
  6 + <i class="el-icon-delete"></i>
  7 + </span>
  8 + </div>
  9 +
  10 + <div v-if="photos.length < imageCount" class="upload-btn" @click="triggerUpload">
  11 + <i class="el-icon-plus"></i>
  12 + </div>
  13 +
  14 + <input
  15 + ref="fileInput"
  16 + type="file"
  17 + accept="image/*"
  18 + style="display: none"
  19 + @change="handleFileChange"
  20 + />
  21 + </div>
  22 +</template>
  23 +
  24 +<script>
  25 +import { uploadFile } from '@/api/common'
  26 +
  27 +export default {
  28 + name: 'UploadImageUrl',
  29 + props: {
  30 + imageCount: {
  31 + type: Number,
  32 + default: 99
  33 + }
  34 + },
  35 + data() {
  36 + return {
  37 + photos: [], // 用于显示的图片数组
  38 + photosUrl: [] // 实际存储的图片信息数组
  39 + }
  40 + },
  41 + methods: {
  42 + triggerUpload() {
  43 + this.$refs.fileInput.click()
  44 + },
  45 + async handleFileChange(event) {
  46 + const files = event.target.files
  47 + if (!files || files.length === 0) return
  48 +
  49 + const file = files[0]
  50 + if (file.size > 2 * 1024 * 1024) {
  51 + this.$message.error(this.$t('uploadImage.validate.sizeLimit'))
  52 + return
  53 + }
  54 +
  55 + try {
  56 + // 显示本地预览
  57 + const reader = new FileReader()
  58 + reader.onload = (e) => {
  59 + this.photos.push(e.target.result)
  60 + }
  61 + reader.readAsDataURL(file)
  62 +
  63 + // 上传到服务器
  64 + const formData = new FormData()
  65 + formData.append('uploadFile', file)
  66 + formData.append('communityId', this.$store.getters.communityId)
  67 +
  68 + const { data } = await uploadFile(formData)
  69 + this.photosUrl.push(data)
  70 + this.$emit('change', this.photosUrl)
  71 + } catch (error) {
  72 + this.$message.error(this.$t('uploadImage.message.uploadFailed'))
  73 + } finally {
  74 + event.target.value = null
  75 + }
  76 + },
  77 + removeImage(index) {
  78 + this.photos.splice(index, 1)
  79 + this.photosUrl.splice(index, 1)
  80 + this.$emit('change', this.photosUrl)
  81 + },
  82 + handleImageError(e) {
  83 + e.target.src = '/img/noPhoto.jpg'
  84 + },
  85 + getImageUrl(image) {
  86 + if (typeof image === 'string') {
  87 + if (image.startsWith('http') || image.startsWith('https') || image.startsWith('data:')) {
  88 + return image
  89 + }
  90 + return `/callComponent/download/getFile/file?fileId=${image}&communityId=-1&time=${new Date().getTime()}`
  91 + }
  92 + return image.url || '/img/noPhoto.jpg'
  93 + },
  94 + clear() {
  95 + this.photos = []
  96 + this.photosUrl = []
  97 + },
  98 + setImages(images) {
  99 + this.photos = []
  100 + this.photosUrl = []
  101 + images.forEach(image => {
  102 + if (image.startsWith('data:')) {
  103 + this.photos.push(image)
  104 + return
  105 + }
  106 + this.photosUrl.push({ fileId: image, url: image })
  107 + this.photos.push(this.getImageUrl(image))
  108 + })
  109 + }
  110 + }
  111 +}
  112 +</script>
  113 +
  114 +<style lang="scss" scoped>
  115 +.upload-image-container {
  116 + display: flex;
  117 + flex-wrap: wrap;
  118 + gap: 10px;
  119 +
  120 + .image-item {
  121 + position: relative;
  122 + width: 100px;
  123 + height: 100px;
  124 + border: 1px dashed #dcdfe6;
  125 + border-radius: 4px;
  126 + overflow: hidden;
  127 +
  128 + img {
  129 + width: 100%;
  130 + height: 100%;
  131 + object-fit: cover;
  132 + }
  133 +
  134 + .remove-icon {
  135 + position: absolute;
  136 + top: 0;
  137 + right: 0;
  138 + color: #f56c6c;
  139 + background: rgba(255, 255, 255, 0.7);
  140 + padding: 4px;
  141 + cursor: pointer;
  142 + font-size: 16px;
  143 +
  144 + &:hover {
  145 + color: #f78989;
  146 + }
  147 + }
  148 + }
  149 +
  150 + .upload-btn {
  151 + width: 100px;
  152 + height: 100px;
  153 + border: 1px dashed #dcdfe6;
  154 + border-radius: 4px;
  155 + display: flex;
  156 + justify-content: center;
  157 + align-items: center;
  158 + cursor: pointer;
  159 + color: #8c939d;
  160 + font-size: 28px;
  161 + background-color: #f5f7fa;
  162 +
  163 + &:hover {
  164 + border-color: #409eff;
  165 + color: #409eff;
  166 + }
  167 + }
  168 +}
  169 +</style>
0 \ No newline at end of file 170 \ No newline at end of file
src/components/fee/viewImage.vue
1 <template> 1 <template>
2 - <div v-show="showImage" class="image-viewer">  
3 - <div class="image-container">  
4 - <img :src="url" :style="imgStyle" @error="handleError">  
5 - <i class="el-icon-close close-icon" @click="close"></i> 2 + <div v-show="visible" class="image-viewer-wrapper">
  3 + <div class="image-viewer-mask" @click="close"></div>
  4 + <div class="image-viewer-content">
  5 + <img
  6 + :src="imageUrl"
  7 + :style="imageStyle"
  8 + @error="handleImageError"
  9 + />
  10 + <span class="image-viewer-close" @click="close">
  11 + <i class="el-icon-close"></i>
  12 + </span>
6 </div> 13 </div>
7 </div> 14 </div>
8 </template> 15 </template>
@@ -12,72 +19,100 @@ export default { @@ -12,72 +19,100 @@ export default {
12 name: 'ViewImage', 19 name: 'ViewImage',
13 data() { 20 data() {
14 return { 21 return {
15 - url: '',  
16 - showImage: false,  
17 - imgWidth: 800,  
18 - imgHeight: 800 22 + visible: false,
  23 + imageUrl: '',
  24 + imageWidth: 0,
  25 + imageHeight: 0
19 } 26 }
20 }, 27 },
21 computed: { 28 computed: {
22 - imgStyle() { 29 + imageStyle() {
23 return { 30 return {
24 - width: `${this.imgWidth}px`,  
25 - height: `${this.imgHeight}px` 31 + width: this.imageWidth ? `${this.imageWidth}px` : 'auto',
  32 + height: this.imageHeight ? `${this.imageHeight}px` : 'auto',
  33 + maxWidth: '90vw',
  34 + maxHeight: '90vh'
26 } 35 }
27 } 36 }
28 }, 37 },
29 methods: { 38 methods: {
30 open(url) { 39 open(url) {
31 - this.url = url  
32 - this.showImage = true  
33 - this.calculateSize(url) 40 + this.imageUrl = url
  41 + this.visible = true
  42 + this.$nextTick(() => {
  43 + this.calculateImageSize(url)
  44 + })
34 }, 45 },
35 close() { 46 close() {
36 - this.showImage = false 47 + this.visible = false
  48 + this.imageUrl = ''
  49 + this.imageWidth = 0
  50 + this.imageHeight = 0
37 }, 51 },
38 - calculateSize(url) { 52 + handleImageError(e) {
  53 + e.target.src = '/img/noPhoto.jpg'
  54 + },
  55 + calculateImageSize(url) {
39 const img = new Image() 56 const img = new Image()
40 img.src = url 57 img.src = url
41 img.onload = () => { 58 img.onload = () => {
42 - const ratio = img.width / img.height  
43 - this.imgWidth = 800  
44 - this.imgHeight = 800 / ratio 59 + const maxWidth = window.innerWidth * 0.8
  60 + const maxHeight = window.innerHeight * 0.8
  61 + const ratio = Math.min(maxWidth / img.width, maxHeight / img.height, 1)
  62 + this.imageWidth = img.width * ratio
  63 + this.imageHeight = img.height * ratio
45 } 64 }
46 - },  
47 - handleError() {  
48 - this.url = '/img/noPhoto.jpg'  
49 } 65 }
50 } 66 }
51 } 67 }
52 </script> 68 </script>
53 69
54 -<style scoped>  
55 -.image-viewer { 70 +<style lang="scss" scoped>
  71 +.image-viewer-wrapper {
56 position: fixed; 72 position: fixed;
57 top: 0; 73 top: 0;
58 left: 0; 74 left: 0;
59 right: 0; 75 right: 0;
60 bottom: 0; 76 bottom: 0;
61 - background-color: rgba(0, 0, 0, 0.5);  
62 z-index: 9999; 77 z-index: 9999;
63 display: flex; 78 display: flex;
64 justify-content: center; 79 justify-content: center;
65 align-items: center; 80 align-items: center;
66 -}  
67 81
68 -.image-container {  
69 - position: relative;  
70 - background-color: #fff;  
71 - padding: 20px;  
72 - border-radius: 4px;  
73 -} 82 + .image-viewer-mask {
  83 + position: absolute;
  84 + width: 100%;
  85 + height: 100%;
  86 + background-color: rgba(0, 0, 0, 0.7);
  87 + }
  88 +
  89 + .image-viewer-content {
  90 + position: relative;
  91 + z-index: 1;
  92 + padding: 20px;
  93 + background-color: #fff;
  94 + border-radius: 4px;
  95 + text-align: center;
74 96
75 -.close-icon {  
76 - position: absolute;  
77 - top: 10px;  
78 - right: 10px;  
79 - font-size: 24px;  
80 - color: #f56c6c;  
81 - cursor: pointer; 97 + img {
  98 + display: block;
  99 + margin: 0 auto;
  100 + object-fit: contain;
  101 + }
  102 + }
  103 +
  104 + .image-viewer-close {
  105 + position: absolute;
  106 + top: 10px;
  107 + right: 10px;
  108 + font-size: 24px;
  109 + color: #f56c6c;
  110 + cursor: pointer;
  111 + z-index: 2;
  112 +
  113 + &:hover {
  114 + color: #f78989;
  115 + }
  116 + }
82 } 117 }
83 </style> 118 </style>
84 \ No newline at end of file 119 \ No newline at end of file
src/i18n/feeI18n.js
@@ -3,6 +3,9 @@ import { messages as meterWaterManageMessages } from &#39;../views/fee/meterWaterMan @@ -3,6 +3,9 @@ import { messages as meterWaterManageMessages } from &#39;../views/fee/meterWaterMan
3 import { messages as meterTypeManageMessages } from '../views/fee/meterTypeManageLang' 3 import { messages as meterTypeManageMessages } from '../views/fee/meterTypeManageLang'
4 import { messages as returnPayFeeManageMessages } from '../views/fee/returnPayFeeManageLang' 4 import { messages as returnPayFeeManageMessages } from '../views/fee/returnPayFeeManageLang'
5 import { messages as feeDiscountManageMessages } from '../views/fee/feeDiscountManageLang' 5 import { messages as feeDiscountManageMessages } from '../views/fee/feeDiscountManageLang'
  6 +import { messages as applyRoomDiscountManageMessages } from '../views/fee/applyRoomDiscountManageLang'
  7 +import { messages as discountTypeMessages } from '../views/fee/discountTypeLang'
  8 +
6 export const messages = { 9 export const messages = {
7 en: { 10 en: {
8 ...contractCreateFeeMessages.en, 11 ...contractCreateFeeMessages.en,
@@ -10,6 +13,8 @@ export const messages = { @@ -10,6 +13,8 @@ export const messages = {
10 ...meterTypeManageMessages.en, 13 ...meterTypeManageMessages.en,
11 ...returnPayFeeManageMessages.en, 14 ...returnPayFeeManageMessages.en,
12 ...feeDiscountManageMessages.en, 15 ...feeDiscountManageMessages.en,
  16 + ...applyRoomDiscountManageMessages.en,
  17 + ...discountTypeMessages.en,
13 }, 18 },
14 zh: { 19 zh: {
15 ...contractCreateFeeMessages.zh, 20 ...contractCreateFeeMessages.zh,
@@ -17,5 +22,7 @@ export const messages = { @@ -17,5 +22,7 @@ export const messages = {
17 ...meterTypeManageMessages.zh, 22 ...meterTypeManageMessages.zh,
18 ...returnPayFeeManageMessages.zh, 23 ...returnPayFeeManageMessages.zh,
19 ...feeDiscountManageMessages.zh, 24 ...feeDiscountManageMessages.zh,
  25 + ...applyRoomDiscountManageMessages.zh,
  26 + ...discountTypeMessages.zh,
20 } 27 }
21 } 28 }
22 \ No newline at end of file 29 \ No newline at end of file
src/router/feeRouter.js
@@ -20,8 +20,18 @@ export default [ @@ -20,8 +20,18 @@ export default [
20 component: () => import('@/views/fee/returnPayFeeManageList.vue') 20 component: () => import('@/views/fee/returnPayFeeManageList.vue')
21 }, 21 },
22 { 22 {
23 - path:'/pages/property/feeDiscountManage',  
24 - name:'/pages/property/feeDiscountManage', 23 + path: '/pages/property/feeDiscountManage',
  24 + name: '/pages/property/feeDiscountManage',
25 component: () => import('@/views/fee/feeDiscountManageList.vue') 25 component: () => import('@/views/fee/feeDiscountManageList.vue')
  26 + },
  27 + {
  28 + path: '/pages/property/applyRoomDiscountManage',
  29 + name: '/pages/property/applyRoomDiscountManage',
  30 + component: () => import('@/views/fee/applyRoomDiscountManageList.vue')
  31 + },
  32 + {
  33 + path:'/views/fee/discountType',
  34 + name:'/views/fee/discountType',
  35 + component: () => import('@/views/fee/discountTypeList.vue')
26 }, 36 },
27 ] 37 ]
28 \ No newline at end of file 38 \ No newline at end of file
src/views/fee/applyRoomDiscountManageLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + applyRoomDiscount: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + roomName: 'Please enter room name (Building-Unit-Room)',
  7 + applyType: 'Please select application type',
  8 + state: 'Please select status'
  9 + },
  10 + list: {
  11 + title: 'Discount Application'
  12 + },
  13 + table: {
  14 + roomName: 'Room (Building-Unit-Room)',
  15 + discountId: 'Discount ID',
  16 + discountName: 'Discount Name',
  17 + applyType: 'Application Type',
  18 + createUser: 'Applicant',
  19 + createUserTel: 'Applicant Phone',
  20 + startTime: 'Start Time',
  21 + endTime: 'End Time',
  22 + state: 'Status',
  23 + createTime: 'Create Time',
  24 + inUse: 'Usage Status',
  25 + returnType: 'Return Type',
  26 + returnAmount: 'Return Amount'
  27 + },
  28 + button: {
  29 + discountType: 'Discount Type',
  30 + phoneApply: 'Phone Application',
  31 + export: 'Export',
  32 + check: 'Check',
  33 + record: 'Record',
  34 + review: 'Review',
  35 + edit: 'Edit',
  36 + delete: 'Delete'
  37 + },
  38 + status: {
  39 + notUsed: 'Not Used',
  40 + used: 'Used',
  41 + checkPass: 'Check Passed',
  42 + checkReject: 'Check Rejected',
  43 + reviewPass: 'Review Passed',
  44 + reviewReject: 'Review Rejected'
  45 + },
  46 + returnType: {
  47 + account: 'Account Balance',
  48 + discount: 'Discount'
  49 + },
  50 + discountType: {
  51 + discount: 'Discount (Need Application)'
  52 + },
  53 + returnWay: {
  54 + discount: 'Enjoy Payment Discount',
  55 + account: 'Refund to Balance Account'
  56 + },
  57 + add: {
  58 + title: 'Phone Application',
  59 + room: 'Room',
  60 + roomPlaceholder: 'Required, please fill in room (Building-Unit-Room)',
  61 + applyType: 'Application Type',
  62 + applyTypePlaceholder: 'Required, please select application type',
  63 + feeItem: 'Fee Item',
  64 + feeItemPlaceholder: 'Please select fee item',
  65 + createUser: 'Applicant',
  66 + createUserPlaceholder: 'Required, please fill in applicant',
  67 + createUserTel: 'Applicant Phone',
  68 + createUserTelPlaceholder: 'Required, please fill in applicant phone',
  69 + startTime: 'Start Time',
  70 + startTimePlaceholder: 'Required, please fill in start time',
  71 + endTime: 'End Time',
  72 + endTimePlaceholder: 'Required, please fill in end time',
  73 + createRemark: 'Application Description',
  74 + createRemarkPlaceholder: 'Required, please fill in application description',
  75 + images: 'Image Materials'
  76 + },
  77 + edit: {
  78 + title: 'Check Room',
  79 + startTime: 'Start Time',
  80 + startTimePlaceholder: 'Required, please fill in start time',
  81 + endTime: 'End Time',
  82 + endTimePlaceholder: 'Required, please fill in end time',
  83 + state: 'Check Status',
  84 + statePlaceholder: 'Required, please select status',
  85 + createRemark: 'Application Description',
  86 + checkRemark: 'Check Description',
  87 + checkRemarkPlaceholder: 'Required, please fill in check description',
  88 + images: 'Image Materials'
  89 + },
  90 + editRecord: {
  91 + title: 'Edit Discount Application',
  92 + ardId: 'Application ID',
  93 + roomName: 'Room (Building-Unit-Room)',
  94 + applyTypeName: 'Application Type',
  95 + createUserName: 'Applicant',
  96 + createUserTel: 'Applicant Phone',
  97 + startTime: 'Start Time',
  98 + startTimePlaceholder: 'Required, please fill in start time',
  99 + endTime: 'End Time',
  100 + endTimePlaceholder: 'Required, please fill in end time',
  101 + stateName: 'Status'
  102 + },
  103 + review: {
  104 + title: 'Review',
  105 + discountType: 'Discount Type',
  106 + discountTypePlaceholder: 'Required, please select discount type',
  107 + discountName: 'Discount Name',
  108 + discountNamePlaceholder: 'Required, please select discount name',
  109 + returnWay: 'Return Way',
  110 + returnWayPlaceholder: 'Please select return way',
  111 + paymentRecords: 'Payment Records',
  112 + refundAmount: 'Return Amount',
  113 + refundAmountPlaceholder: 'Required, please fill in return amount',
  114 + startTime: 'Start Time',
  115 + startTimePlaceholder: 'Required, please fill in start time',
  116 + endTime: 'End Time',
  117 + endTimePlaceholder: 'Required, please fill in end time',
  118 + state: 'Review Status',
  119 + statePlaceholder: 'Required, please select status',
  120 + createRemark: 'Application Remark',
  121 + checkRemark: 'Check Remark',
  122 + reviewRemark: 'Review Remark',
  123 + reviewRemarkPlaceholder: 'Required, please fill in review description',
  124 + images: 'Image Materials'
  125 + },
  126 + delete: {
  127 + title: 'Please confirm your operation',
  128 + confirmText: 'Confirm to delete the room discount application?'
  129 + },
  130 + validate: {
  131 + roomRequired: 'Room cannot be empty',
  132 + roomFormat: 'Room format error',
  133 + roomNotFound: 'Room not found',
  134 + applyTypeRequired: 'Application type cannot be empty',
  135 + feeIdRequired: 'Fee item cannot be empty',
  136 + createUserRequired: 'Applicant cannot be empty',
  137 + createUserFormat: 'Applicant format error',
  138 + createUserTelRequired: 'Applicant phone cannot be empty',
  139 + createUserTelFormat: 'Applicant phone format error',
  140 + startTimeRequired: 'Start time cannot be empty',
  141 + endTimeRequired: 'End time cannot be empty',
  142 + dateInvalid: 'End time must be greater than start time',
  143 + createRemarkRequired: 'Application description cannot be empty',
  144 + createRemarkFormat: 'Application description too long',
  145 + checkRemarkRequired: 'Check description cannot be empty',
  146 + checkRemarkFormat: 'Check description too long',
  147 + reviewRemarkRequired: 'Review description cannot be empty',
  148 + reviewRemarkFormat: 'Review description too long',
  149 + stateRequired: 'Status cannot be empty',
  150 + ardIdRequired: 'Application ID cannot be empty',
  151 + discountIdRequired: 'Discount name cannot be empty',
  152 + discountTypeRequired: 'Discount type cannot be empty',
  153 + returnWayRequired: 'Return way cannot be empty',
  154 + refundAmountRequired: 'Return amount cannot be empty',
  155 + refundAmountFormat: 'Return amount format error'
  156 + },
  157 + message: {
  158 + saveSuccess: 'Application successful',
  159 + saveFailed: 'Application failed',
  160 + updateSuccess: 'Update successful',
  161 + updateFailed: 'Update failed',
  162 + reviewSuccess: 'Review successful',
  163 + reviewFailed: 'Review failed',
  164 + deleteSuccess: 'Delete successful',
  165 + deleteFailed: 'Delete failed',
  166 + fetchError: 'Failed to get data'
  167 + }
  168 + }
  169 + },
  170 + zh: {
  171 + applyRoomDiscount: {
  172 + search: {
  173 + title: '查询条件',
  174 + roomName: '请输入房屋名称(楼栋-单元-房屋)',
  175 + applyType: '请选择申请类型',
  176 + state: '请选择状态'
  177 + },
  178 + list: {
  179 + title: '优惠申请'
  180 + },
  181 + table: {
  182 + roomName: '房屋(楼栋-单元-房屋)',
  183 + discountId: '折扣ID',
  184 + discountName: '折扣名称',
  185 + applyType: '申请类型',
  186 + createUser: '申请人',
  187 + createUserTel: '申请电话',
  188 + startTime: '开始时间',
  189 + endTime: '结束时间',
  190 + state: '状态',
  191 + createTime: '创建时间',
  192 + inUse: '使用状态',
  193 + returnType: '返还类型',
  194 + returnAmount: '返还金额'
  195 + },
  196 + button: {
  197 + discountType: '优惠类型',
  198 + phoneApply: '电话申请',
  199 + export: '导出',
  200 + check: '验房',
  201 + record: '跟踪记录',
  202 + review: '审核',
  203 + edit: '修改',
  204 + delete: '删除'
  205 + },
  206 + status: {
  207 + notUsed: '未使用',
  208 + used: '已使用',
  209 + checkPass: '验房通过',
  210 + checkReject: '验房不通过',
  211 + reviewPass: '审批通过',
  212 + reviewReject: '审批不通过'
  213 + },
  214 + returnType: {
  215 + account: '账户余额',
  216 + discount: '折扣'
  217 + },
  218 + discountType: {
  219 + discount: '优惠(需要申请)'
  220 + },
  221 + returnWay: {
  222 + discount: '享受缴纳折扣',
  223 + account: '预缴金额返还至余额账户'
  224 + },
  225 + add: {
  226 + title: '电话申请',
  227 + room: '房屋',
  228 + roomPlaceholder: '必填,请填写房屋 楼栋-单元-房屋',
  229 + applyType: '申请类型',
  230 + applyTypePlaceholder: '必填,请选择申请类型',
  231 + feeItem: '费用项目',
  232 + feeItemPlaceholder: '请选择费用项目',
  233 + createUser: '申请人',
  234 + createUserPlaceholder: '必填,请填写申请人',
  235 + createUserTel: '申请电话',
  236 + createUserTelPlaceholder: '必填,请填写申请电话',
  237 + startTime: '开始时间',
  238 + startTimePlaceholder: '必填,请填写开始时间',
  239 + endTime: '结束时间',
  240 + endTimePlaceholder: '必填,请填写结束时间',
  241 + createRemark: '申请说明',
  242 + createRemarkPlaceholder: '必填,请填写申请说明',
  243 + images: '图片材料'
  244 + },
  245 + edit: {
  246 + title: '验房',
  247 + startTime: '开始时间',
  248 + startTimePlaceholder: '必填,请填写开始时间',
  249 + endTime: '结束时间',
  250 + endTimePlaceholder: '必填,请填写结束时间',
  251 + state: '验房状态',
  252 + statePlaceholder: '必填,请选择状态',
  253 + createRemark: '申请说明',
  254 + checkRemark: '验房说明',
  255 + checkRemarkPlaceholder: '必填,请填写验房说明',
  256 + images: '图片资料'
  257 + },
  258 + editRecord: {
  259 + title: '修改优惠申请',
  260 + ardId: '申请ID',
  261 + roomName: '房屋(楼栋-单元-房屋)',
  262 + applyTypeName: '申请类型',
  263 + createUserName: '申请人',
  264 + createUserTel: '申请电话',
  265 + startTime: '开始时间',
  266 + startTimePlaceholder: '必填,请填写开始时间',
  267 + endTime: '结束时间',
  268 + endTimePlaceholder: '必填,请填写结束时间',
  269 + stateName: '状态'
  270 + },
  271 + review: {
  272 + title: '审批',
  273 + discountType: '折扣类型',
  274 + discountTypePlaceholder: '必填,请选择折扣类型',
  275 + discountName: '折扣名称',
  276 + discountNamePlaceholder: '必填,请选择折扣名称',
  277 + returnWay: '返还方式',
  278 + returnWayPlaceholder: '请选择返还方式',
  279 + paymentRecords: '缴费记录',
  280 + refundAmount: '返还金额',
  281 + refundAmountPlaceholder: '必填,请填写返还金额',
  282 + startTime: '开始时间',
  283 + startTimePlaceholder: '必填,请填写开始时间',
  284 + endTime: '结束时间',
  285 + endTimePlaceholder: '必填,请填写结束时间',
  286 + state: '审批状态',
  287 + statePlaceholder: '必填,请选择状态',
  288 + createRemark: '申请备注',
  289 + checkRemark: '验房备注',
  290 + reviewRemark: '审批备注',
  291 + reviewRemarkPlaceholder: '必填,请填写审批说明',
  292 + images: '图片资料'
  293 + },
  294 + delete: {
  295 + title: '请确认您的操作',
  296 + confirmText: '确定删除房屋折扣申请?'
  297 + },
  298 + validate: {
  299 + roomRequired: '房屋不能为空',
  300 + roomFormat: '房屋格式错误',
  301 + roomNotFound: '未找到房屋',
  302 + applyTypeRequired: '申请类型不能为空',
  303 + feeIdRequired: '费用项不能为空',
  304 + createUserRequired: '申请人不能为空',
  305 + createUserFormat: '申请人错误',
  306 + createUserTelRequired: '申请电话不能为空',
  307 + createUserTelFormat: '申请电话错误',
  308 + startTimeRequired: '开始时间不能为空',
  309 + endTimeRequired: '结束时间不能为空',
  310 + dateInvalid: '结束时间必须大于开始时间',
  311 + createRemarkRequired: '申请说明不能为空',
  312 + createRemarkFormat: '申请说明过长',
  313 + checkRemarkRequired: '验房说明不能为空',
  314 + checkRemarkFormat: '验房说明过长',
  315 + reviewRemarkRequired: '审批说明不能为空',
  316 + reviewRemarkFormat: '审批说明过长',
  317 + stateRequired: '状态不能为空',
  318 + ardIdRequired: '申请ID不能为空',
  319 + discountIdRequired: '折扣名称不能为空',
  320 + discountTypeRequired: '折扣类型不能为空',
  321 + returnWayRequired: '返还方式不能为空',
  322 + refundAmountRequired: '返还金额不能为空',
  323 + refundAmountFormat: '返还金额格式错误'
  324 + },
  325 + message: {
  326 + saveSuccess: '申请成功',
  327 + saveFailed: '申请失败',
  328 + updateSuccess: '修改成功',
  329 + updateFailed: '修改失败',
  330 + reviewSuccess: '审批成功',
  331 + reviewFailed: '审批失败',
  332 + deleteSuccess: '删除成功',
  333 + deleteFailed: '删除失败',
  334 + fetchError: '获取数据失败'
  335 + }
  336 + }
  337 + }
  338 +}
0 \ No newline at end of file 339 \ No newline at end of file
src/views/fee/applyRoomDiscountManageList.vue 0 → 100644
  1 +<template>
  2 + <div class="apply-room-discount-container">
  3 + <!-- 查询条件 -->
  4 + <el-card class="search-wrapper">
  5 + <div slot="header" class="flex justify-between">
  6 + <span>{{ $t('applyRoomDiscount.search.title') }}</span>
  7 + </div>
  8 + <el-row :gutter="20">
  9 + <el-col :span="6">
  10 + <el-input v-model.trim="searchForm.roomName" :placeholder="$t('applyRoomDiscount.search.roomName')"
  11 + clearable />
  12 + </el-col>
  13 + <el-col :span="6">
  14 + <el-select v-model="searchForm.applyType" :placeholder="$t('applyRoomDiscount.search.applyType')"
  15 + style="width:100%">
  16 + <el-option v-for="item in applyTypes" :key="item.applyType" :label="item.typeName"
  17 + :value="item.applyType" />
  18 + </el-select>
  19 + </el-col>
  20 + <el-col :span="6">
  21 + <el-select v-model="searchForm.state" :placeholder="$t('applyRoomDiscount.search.state')" style="width:100%">
  22 + <el-option v-for="item in states" :key="item.statusCd" :label="item.name" :value="item.statusCd" />
  23 + </el-select>
  24 + </el-col>
  25 + <el-col :span="6">
  26 + <el-button type="primary" @click="handleSearch">
  27 + {{ $t('common.search') }}
  28 + </el-button>
  29 + <el-button @click="handleReset">
  30 + {{ $t('common.reset') }}
  31 + </el-button>
  32 + </el-col>
  33 + </el-row>
  34 + </el-card>
  35 +
  36 + <!-- 列表 -->
  37 + <el-card class="list-wrapper">
  38 + <div slot="header" class="flex justify-between">
  39 + <span>{{ $t('applyRoomDiscount.list.title') }}</span>
  40 + <div style="float: right;">
  41 + <el-button type="primary" size="small" @click="handleOpenDiscountType">
  42 + {{ $t('applyRoomDiscount.button.discountType') }}
  43 + </el-button>
  44 + <el-button type="primary" size="small" @click="handleOpenAddApply">
  45 + <i class="el-icon-plus"></i>
  46 + {{ $t('applyRoomDiscount.button.phoneApply') }}
  47 + </el-button>
  48 + <el-button type="primary" size="small" @click="handleExport">
  49 + <i class="el-icon-download"></i>
  50 + {{ $t('applyRoomDiscount.button.export') }}
  51 + </el-button>
  52 + </div>
  53 + </div>
  54 +
  55 + <el-table v-loading="loading" :data="tableData" border style="width: 100%">
  56 + <el-table-column prop="roomName" :label="$t('applyRoomDiscount.table.roomName')" align="center" />
  57 + <el-table-column prop="discountId" :label="$t('applyRoomDiscount.table.discountId')" align="center" />
  58 + <el-table-column prop="discountName" :label="$t('applyRoomDiscount.table.discountName')" align="center" />
  59 + <el-table-column prop="applyTypeName" :label="$t('applyRoomDiscount.table.applyType')" align="center" />
  60 + <el-table-column prop="createUserName" :label="$t('applyRoomDiscount.table.createUser')" align="center" />
  61 + <el-table-column prop="createUserTel" :label="$t('applyRoomDiscount.table.createUserTel')" align="center" />
  62 + <el-table-column prop="startTime" :label="$t('applyRoomDiscount.table.startTime')" align="center" />
  63 + <el-table-column prop="endTime" :label="$t('applyRoomDiscount.table.endTime')" align="center" />
  64 + <el-table-column prop="stateName" :label="$t('applyRoomDiscount.table.state')" align="center" />
  65 + <el-table-column prop="createTime" :label="$t('applyRoomDiscount.table.createTime')" align="center" />
  66 + <el-table-column :label="$t('applyRoomDiscount.table.inUse')" align="center">
  67 + <template slot-scope="scope">
  68 + {{ scope.row.inUse === '0' ? $t('applyRoomDiscount.status.notUsed') : $t('applyRoomDiscount.status.used') }}
  69 + </template>
  70 + </el-table-column>
  71 + <el-table-column :label="$t('applyRoomDiscount.table.returnType')" align="center">
  72 + <template slot-scope="scope">
  73 + {{ scope.row.discountId ? (scope.row.returnWay === '1002' ? $t('applyRoomDiscount.returnType.account') :
  74 + $t('applyRoomDiscount.returnType.discount')) : '-' }}
  75 + </template>
  76 + </el-table-column>
  77 + <el-table-column :label="$t('applyRoomDiscount.table.returnAmount')" align="center">
  78 + <template slot-scope="scope">
  79 + {{ scope.row.returnAmount ? scope.row.returnAmount : '-' }}
  80 + </template>
  81 + </el-table-column>
  82 + <el-table-column :label="$t('common.operation')" align="center" width="300" fixed="right">
  83 + <template slot-scope="scope">
  84 + <el-button v-if="scope.row.state === '1' && hasPrivilege('502021010723590006')" size="mini"
  85 + @click="handleOpenCheck(scope.row)">
  86 + {{ $t('applyRoomDiscount.button.check') }}
  87 + </el-button>
  88 + <el-button size="mini" @click="handleOpenRecord(scope.row)">
  89 + {{ $t('applyRoomDiscount.button.record') }}
  90 + </el-button>
  91 + <el-button v-if="scope.row.state === '2' && hasPrivilege('502021010761730007')" size="mini"
  92 + @click="handleOpenReview(scope.row)">
  93 + {{ $t('applyRoomDiscount.button.review') }}
  94 + </el-button>
  95 + <el-button size="mini" @click="handleOpenEdit(scope.row)">
  96 + {{ $t('common.edit') }}
  97 + </el-button>
  98 + <el-button size="mini" type="danger" @click="handleOpenDelete(scope.row)">
  99 + {{ $t('common.delete') }}
  100 + </el-button>
  101 + </template>
  102 + </el-table-column>
  103 + </el-table>
  104 +
  105 + <el-pagination :current-page.sync="pagination.current" :page-sizes="[10, 20, 30, 50]" :page-size="pagination.size"
  106 + :total="pagination.total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
  107 + @current-change="handleCurrentChange" />
  108 + </el-card>
  109 +
  110 + <!-- 子组件 -->
  111 + <add-apply-room-discount ref="addApplyRoomDiscount" @success="handleSuccess" />
  112 + <edit-apply-room-discount ref="editApplyRoomDiscount" @success="handleSuccess" />
  113 + <edit-apply-room-discount-record ref="editApplyRoomDiscountRecord" @success="handleSuccess" />
  114 + <review-apply-room-discount ref="reviewApplyRoomDiscount" @success="handleSuccess" />
  115 + <delete-apply-room-discount ref="deleteApplyRoomDiscount" @success="handleSuccess" />
  116 + <view-image ref="viewImage" />
  117 + </div>
  118 +</template>
  119 +
  120 +<script>
  121 +import { getDict } from '@/api/community/communityApi'
  122 +import { queryApplyRoomDiscount, queryApplyRoomDiscountType } from '@/api/fee/applyRoomDiscountManageApi'
  123 +import AddApplyRoomDiscount from '@/components/fee/addApplyRoomDiscount'
  124 +import EditApplyRoomDiscount from '@/components/fee/editApplyRoomDiscount'
  125 +import EditApplyRoomDiscountRecord from '@/components/fee/editApplyRoomDiscountRecord'
  126 +import ReviewApplyRoomDiscount from '@/components/fee/reviewApplyRoomDiscount'
  127 +import DeleteApplyRoomDiscount from '@/components/fee/deleteApplyRoomDiscount'
  128 +import ViewImage from '@/components/system/viewImage'
  129 +
  130 +export default {
  131 + name: 'ApplyRoomDiscountManageList',
  132 + components: {
  133 + AddApplyRoomDiscount,
  134 + EditApplyRoomDiscount,
  135 + EditApplyRoomDiscountRecord,
  136 + ReviewApplyRoomDiscount,
  137 + DeleteApplyRoomDiscount,
  138 + ViewImage
  139 + },
  140 + data() {
  141 + return {
  142 + loading: false,
  143 + searchForm: {
  144 + roomName: '',
  145 + applyType: '',
  146 + state: '',
  147 + communityId: ''
  148 + },
  149 + tableData: [],
  150 + applyTypes: [],
  151 + states: [],
  152 + pagination: {
  153 + current: 1,
  154 + size: 10,
  155 + total: 0
  156 + }
  157 + }
  158 + },
  159 + created() {
  160 + this.getList()
  161 + this.getApplyTypes()
  162 + this.getDictData()
  163 + },
  164 + methods: {
  165 + async getList() {
  166 + try {
  167 + this.loading = true
  168 + const params = {
  169 + page: this.pagination.current,
  170 + row: this.pagination.size,
  171 + ...this.searchForm
  172 + }
  173 + const { data, total } = await queryApplyRoomDiscount(params)
  174 + this.tableData = data
  175 + this.pagination.total = total
  176 + } catch (error) {
  177 + this.$message.error(this.$t('applyRoomDiscount.fetchError'))
  178 + } finally {
  179 + this.loading = false
  180 + }
  181 + },
  182 + async getApplyTypes() {
  183 + try {
  184 + const { data } = await queryApplyRoomDiscountType({
  185 + page: 1,
  186 + row: 50,
  187 + communityId: this.searchForm.communityId
  188 + })
  189 + this.applyTypes = data
  190 + } catch (error) {
  191 + console.error('获取申请类型失败:', error)
  192 + }
  193 + },
  194 + async getDictData() {
  195 + try {
  196 + const data = await getDict('apply_room_discount', 'state')
  197 + this.states = data
  198 + } catch (error) {
  199 + console.error('获取字典数据失败:', error)
  200 + }
  201 + },
  202 + handleSearch() {
  203 + this.pagination.current = 1
  204 + this.getList()
  205 + },
  206 + handleReset() {
  207 + this.searchForm = {
  208 + roomName: '',
  209 + applyType: '',
  210 + state: '',
  211 + communityId: ''
  212 + }
  213 + this.handleSearch()
  214 + },
  215 + handleSizeChange(val) {
  216 + this.pagination.size = val
  217 + this.getList()
  218 + },
  219 + handleCurrentChange(val) {
  220 + this.pagination.current = val
  221 + this.getList()
  222 + },
  223 + handleOpenAddApply() {
  224 + this.$refs.addApplyRoomDiscount.open()
  225 + },
  226 + handleOpenEdit(row) {
  227 + this.$refs.editApplyRoomDiscountRecord.open(row)
  228 + },
  229 + handleOpenCheck(row) {
  230 + this.$refs.editApplyRoomDiscount.open(row)
  231 + },
  232 + handleOpenRecord(row) {
  233 + this.$router.push({
  234 + path: '/pages/property/listApplyRoomDiscountRecord',
  235 + query: {
  236 + ardId: row.ardId,
  237 + roomId: row.roomId,
  238 + roomName: row.roomName,
  239 + state: row.state,
  240 + stateName: row.stateName
  241 + }
  242 + })
  243 + },
  244 + handleOpenReview(row) {
  245 + this.$refs.reviewApplyRoomDiscount.open(row)
  246 + },
  247 + handleOpenDelete(row) {
  248 + this.$refs.deleteApplyRoomDiscount.open(row)
  249 + },
  250 + handleOpenDiscountType() {
  251 + this.$router.push('/views/fee/discountType')
  252 + },
  253 + handleExport() {
  254 + // 导出逻辑
  255 + },
  256 + handleSuccess() {
  257 + this.getList()
  258 + },
  259 + }
  260 +}
  261 +</script>
  262 +
  263 +<style lang="scss" scoped>
  264 +.apply-room-discount-container {
  265 + padding: 20px;
  266 +
  267 + .search-wrapper {
  268 + margin-bottom: 20px;
  269 +
  270 + .el-row {
  271 + margin-bottom: -20px;
  272 + }
  273 +
  274 + .el-col {
  275 + margin-bottom: 20px;
  276 + }
  277 + }
  278 +
  279 + .list-wrapper {
  280 + margin-bottom: 20px;
  281 +
  282 + .el-pagination {
  283 + margin-top: 20px;
  284 + text-align: right;
  285 + }
  286 + }
  287 +}
  288 +</style>
0 \ No newline at end of file 289 \ No newline at end of file
src/views/fee/discountTypeLang.js 0 → 100644
  1 +export const messages = {
  2 + en: {
  3 + discountType: {
  4 + search: {
  5 + title: 'Search Conditions',
  6 + applyType: 'Please enter type code',
  7 + typeName: 'Please enter type name'
  8 + },
  9 + list: {
  10 + title: 'Discount Types'
  11 + },
  12 + table: {
  13 + applyType: 'Type Code',
  14 + typeName: 'Type Name',
  15 + typeDesc: 'Type Description',
  16 + createTime: 'Create Time'
  17 + },
  18 + form: {
  19 + applyType: 'Type Code',
  20 + typeName: 'Type Name',
  21 + typeNamePlaceholder: 'Required, please enter type name',
  22 + typeDesc: 'Type Description',
  23 + typeDescPlaceholder: 'Optional, please enter type description'
  24 + },
  25 + validate: {
  26 + applyTypeRequired: 'Type code is required',
  27 + typeNameRequired: 'Type name is required',
  28 + typeNameMaxLength: 'Type name max length is 64',
  29 + typeDescMaxLength: 'Type description max length is 512'
  30 + },
  31 + add: {
  32 + title: 'Add Discount Type',
  33 + success: 'Add success'
  34 + },
  35 + edit: {
  36 + title: 'Edit Discount Type',
  37 + success: 'Edit success'
  38 + },
  39 + delete: {
  40 + title: 'Delete Confirmation',
  41 + confirmText: 'Are you sure to delete this discount type?',
  42 + success: 'Delete success'
  43 + },
  44 + fetchError: 'Failed to fetch discount types'
  45 + },
  46 + common: {
  47 + search: 'Search',
  48 + add: 'Add',
  49 + edit: 'Edit',
  50 + delete: 'Delete',
  51 + back: 'Back',
  52 + cancel: 'Cancel',
  53 + confirm: 'Confirm',
  54 + operation: 'Operation'
  55 + }
  56 + },
  57 + zh: {
  58 + discountType: {
  59 + search: {
  60 + title: '查询条件',
  61 + applyType: '请输入类型编码',
  62 + typeName: '请输入类型名称'
  63 + },
  64 + list: {
  65 + title: '优惠类型'
  66 + },
  67 + table: {
  68 + applyType: '类型编码',
  69 + typeName: '类型名称',
  70 + typeDesc: '类型描述',
  71 + createTime: '创建时间'
  72 + },
  73 + form: {
  74 + applyType: '类型编码',
  75 + typeName: '类型名称',
  76 + typeNamePlaceholder: '必填,请填写类型名称',
  77 + typeDesc: '类型描述',
  78 + typeDescPlaceholder: '选填,请填写类型描述'
  79 + },
  80 + validate: {
  81 + applyTypeRequired: '类型编码不能为空',
  82 + typeNameRequired: '类型名称不能为空',
  83 + typeNameMaxLength: '类型名称最大长度64',
  84 + typeDescMaxLength: '类型描述最大长度512'
  85 + },
  86 + add: {
  87 + title: '添加优惠类型',
  88 + success: '添加成功'
  89 + },
  90 + edit: {
  91 + title: '修改优惠类型',
  92 + success: '修改成功'
  93 + },
  94 + delete: {
  95 + title: '删除确认',
  96 + confirmText: '确定删除该优惠类型吗?',
  97 + success: '删除成功'
  98 + },
  99 + fetchError: '获取优惠类型失败'
  100 + },
  101 + common: {
  102 + search: '查询',
  103 + add: '添加',
  104 + edit: '修改',
  105 + delete: '删除',
  106 + back: '返回',
  107 + cancel: '取消',
  108 + confirm: '确认',
  109 + operation: '操作'
  110 + }
  111 + }
  112 +}
0 \ No newline at end of file 113 \ No newline at end of file
src/views/fee/discountTypeList.vue 0 → 100644
  1 +<template>
  2 + <div class="discount-type-container">
  3 + <!-- 查询条件 -->
  4 + <el-card class="search-card">
  5 + <div slot="header" class="clearfix">
  6 + <span>{{ $t('discountType.search.title') }}</span>
  7 + </div>
  8 + <el-row :gutter="20">
  9 + <el-col :span="6">
  10 + <el-input
  11 + v-model.trim="searchForm.applyType"
  12 + :placeholder="$t('discountType.search.applyType')"
  13 + clearable
  14 + />
  15 + </el-col>
  16 + <el-col :span="6">
  17 + <el-input
  18 + v-model.trim="searchForm.typeName"
  19 + :placeholder="$t('discountType.search.typeName')"
  20 + clearable
  21 + />
  22 + </el-col>
  23 + <el-col :span="4">
  24 + <el-button type="primary" @click="handleSearch">
  25 + {{ $t('common.search') }}
  26 + </el-button>
  27 + </el-col>
  28 + </el-row>
  29 + </el-card>
  30 +
  31 + <!-- 列表 -->
  32 + <el-card class="list-card">
  33 + <div slot="header" class="clearfix">
  34 + <span>{{ $t('discountType.list.title') }}</span>
  35 + <div style="float: right;">
  36 + <el-button type="primary" size="small" @click="handleAdd">
  37 + {{ $t('common.add') }}
  38 + </el-button>
  39 + <el-button size="small" @click="goBack">
  40 + {{ $t('common.back') }}
  41 + </el-button>
  42 + </div>
  43 + </div>
  44 +
  45 + <el-table
  46 + v-loading="loading"
  47 + :data="tableData"
  48 + border
  49 + style="width: 100%"
  50 + >
  51 + <el-table-column
  52 + prop="applyType"
  53 + :label="$t('discountType.table.applyType')"
  54 + align="center"
  55 + />
  56 + <el-table-column
  57 + prop="typeName"
  58 + :label="$t('discountType.table.typeName')"
  59 + align="center"
  60 + />
  61 + <el-table-column
  62 + prop="typeDesc"
  63 + :label="$t('discountType.table.typeDesc')"
  64 + align="center"
  65 + />
  66 + <el-table-column
  67 + prop="createTime"
  68 + :label="$t('discountType.table.createTime')"
  69 + align="center"
  70 + />
  71 + <el-table-column
  72 + :label="$t('common.operation')"
  73 + align="center"
  74 + width="200"
  75 + >
  76 + <template slot-scope="scope">
  77 + <el-button
  78 + size="mini"
  79 + type="primary"
  80 + @click="handleEdit(scope.row)"
  81 + >
  82 + {{ $t('common.edit') }}
  83 + </el-button>
  84 + <el-button
  85 + size="mini"
  86 + type="danger"
  87 + @click="handleDelete(scope.row)"
  88 + >
  89 + {{ $t('common.delete') }}
  90 + </el-button>
  91 + </template>
  92 + </el-table-column>
  93 + </el-table>
  94 +
  95 + <el-pagination
  96 + :current-page.sync="page.current"
  97 + :page-sizes="[10, 20, 30, 50]"
  98 + :page-size="page.size"
  99 + :total="page.total"
  100 + layout="total, sizes, prev, pager, next, jumper"
  101 + @size-change="handleSizeChange"
  102 + @current-change="handleCurrentChange"
  103 + />
  104 + </el-card>
  105 +
  106 + <!-- 组件 -->
  107 + <add-apply-room-discount-type ref="addDialog" @success="handleSuccess" />
  108 + <edit-apply-room-discount-type ref="editDialog" @success="handleSuccess" />
  109 + <delete-apply-room-discount-type ref="deleteDialog" @success="handleSuccess" />
  110 + </div>
  111 +</template>
  112 +
  113 +<script>
  114 +import { queryApplyRoomDiscountType } from '@/api/fee/discountTypeApi'
  115 +import { getCommunityId } from '@/api/community/communityApi'
  116 +import AddApplyRoomDiscountType from '@/components/fee/addApplyRoomDiscountType'
  117 +import EditApplyRoomDiscountType from '@/components/fee/editApplyRoomDiscountType'
  118 +import DeleteApplyRoomDiscountType from '@/components/fee/deleteApplyRoomDiscountType'
  119 +
  120 +export default {
  121 + name: 'DiscountTypeList',
  122 + components: {
  123 + AddApplyRoomDiscountType,
  124 + EditApplyRoomDiscountType,
  125 + DeleteApplyRoomDiscountType
  126 + },
  127 + data() {
  128 + return {
  129 + loading: false,
  130 + searchForm: {
  131 + applyType: '',
  132 + typeName: '',
  133 + communityId: ''
  134 + },
  135 + tableData: [],
  136 + page: {
  137 + current: 1,
  138 + size: 10,
  139 + total: 0
  140 + }
  141 + }
  142 + },
  143 + created() {
  144 + this.getList()
  145 + },
  146 + methods: {
  147 + async getList() {
  148 + try {
  149 + this.loading = true
  150 + this.searchForm.communityId = getCommunityId()
  151 + const params = {
  152 + page: this.page.current,
  153 + row: this.page.size,
  154 + ...this.searchForm
  155 + }
  156 + const { data, total } = await queryApplyRoomDiscountType(params)
  157 + this.tableData = data
  158 + this.page.total = total
  159 + } catch (error) {
  160 + this.$message.error(this.$t('discountType.fetchError'))
  161 + } finally {
  162 + this.loading = false
  163 + }
  164 + },
  165 + handleSearch() {
  166 + this.page.current = 1
  167 + this.getList()
  168 + },
  169 + handleAdd() {
  170 + this.$refs.addDialog.open()
  171 + },
  172 + handleEdit(row) {
  173 + this.$refs.editDialog.open(row)
  174 + },
  175 + handleDelete(row) {
  176 + this.$refs.deleteDialog.open(row)
  177 + },
  178 + handleSuccess() {
  179 + this.getList()
  180 + },
  181 + handleSizeChange(val) {
  182 + this.page.size = val
  183 + this.getList()
  184 + },
  185 + handleCurrentChange(val) {
  186 + this.page.current = val
  187 + this.getList()
  188 + },
  189 + goBack() {
  190 + this.$router.go(-1)
  191 + }
  192 + }
  193 +}
  194 +</script>
  195 +
  196 +<style lang="scss" scoped>
  197 +.discount-type-container {
  198 + padding: 20px;
  199 +
  200 + .search-card {
  201 + margin-bottom: 20px;
  202 + }
  203 +
  204 + .el-pagination {
  205 + margin-top: 20px;
  206 + text-align: right;
  207 + }
  208 +}
  209 +</style>
0 \ No newline at end of file 210 \ No newline at end of file