| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834 |
- <template>
- <div class="limit-purchase-rule-container">
- <!-- 规则列表区域 -->
- <el-card class="rule-list-card">
- <template #header>
- <div class="card-header">
- <span>限购规则列表</span>
- <el-button type="primary" size="small" @click="handleCreateRule">
- 新建规则
- </el-button>
- </div>
- </template>
- <el-table
- v-loading="loading.list"
- :data="ruleList"
- border
- style="width: 100%"
- >
- <el-table-column prop="id" label="ID" width="80" />
- <el-table-column prop="name" label="规则名称" min-width="160" />
- <el-table-column label="限购地区" min-width="260">
- <template #default="scope">
- <div v-if="scope.row.regions && scope.row.regions.length">
- <el-tag
- v-for="(region, index) in scope.row.regions"
- :key="index"
- type="danger"
- size="small"
- class="region-tag"
- >
- {{ formatRegionLabel(region) }}
- </el-tag>
- </div>
- <span v-else class="text-muted">未设置</span>
- </template>
- </el-table-column>
- <el-table-column prop="is_default" label="默认规则" width="100">
- <template #default="scope">
- <el-tag :type="scope.row.is_default ? 'success' : 'info'" size="small">
- {{ scope.row.is_default ? '是' : '否' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="status" label="状态" width="100">
- <template #default="scope">
- <el-tag :type="scope.row.status ? 'success' : 'info'" size="small">
- {{ scope.row.status ? '启用' : '停用' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="200" fixed="right">
- <template #default="scope">
- <el-button type="primary" link size="small" @click="handleEditRule(scope.row)">
- 编辑
- </el-button>
- <el-button type="danger" link size="small" @click="handleDeleteRule(scope.row)">
- 删除
- </el-button>
- </template>
- </el-table-column>
- </el-table>
-
- <!-- 分页组件 -->
- <el-pagination
- v-if="pagination.total > 0"
- layout="prev, pager, next"
- :current-page="pagination.page"
- :page-size="pagination.pageSize"
- :total="pagination.total"
- @current-change="handlePageChange"
- style="margin-top: 20px; text-align: right;"
- />
- </el-card>
- <!-- 规则表单弹窗:新建 / 编辑规则基本信息 -->
- <el-dialog
- :title="ruleForm.id ? '编辑限购规则' : '新建限购规则'"
- v-model="ruleDialog.visible"
- width="720px"
- :close-on-click-modal="false"
- >
- <el-form
- ref="ruleFormRef"
- :model="ruleForm"
- :rules="ruleRules"
- label-width="90px"
- >
- <el-form-item label="规则名称" prop="name">
- <el-input v-model="ruleForm.name" maxlength="64" show-word-limit />
- </el-form-item>
- <el-form-item label="是否默认" prop="is_default">
- <el-switch
- v-model="ruleForm.is_default"
- :active-value="true"
- :inactive-value="false"
- />
- <div class="form-tip">设置默认后未设置限购规则的商品都使用该规则</div>
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-switch
- v-model="ruleForm.status"
- :active-value="true"
- :inactive-value="false"
- active-text="启用"
- inactive-text="停用"
- />
- </el-form-item>
- <!-- 在新建 / 编辑规则里直接设置限购地区(本地省市区 JSON 级联) -->
- <el-form-item label="限购地区">
- <div class="region-selector-wrapper">
- <el-form label-width="60px" class="region-form">
- <el-row :gutter="20" align="middle">
- <el-col :span="8">
- <el-form-item label="省份">
- <el-select
- v-model="selectedProvince"
- placeholder="请选择省份"
- filterable
- clearable
- @change="onProvinceChange"
- >
- <el-option
- v-for="p in provinces"
- :key="p.code"
- :label="p.name"
- :value="p.name"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="城市" v-show="!isMunicipalitySelected()">
- <el-select
- v-model="selectedCity"
- placeholder="请选择城市"
- :disabled="!selectedProvince || isMunicipalitySelected()"
- filterable
- clearable
- @change="onCityChange"
- >
- <el-option
- v-for="c in cities"
- :key="c.code"
- :label="c.name"
- :value="c.name"
- />
- </el-select>
- </el-form-item>
- </el-col>
- <el-col :span="8">
- <el-form-item label="区县">
- <el-select
- v-model="selectedArea"
- placeholder="请选择区县"
- :disabled="!selectedProvince || (!selectedCity && !isMunicipalitySelected())"
- filterable
- clearable
- >
- <el-option
- v-for="a in areas"
- :key="a.code"
- :label="a.name"
- :value="a.name"
- />
- </el-select>
- </el-form-item>
- </el-col>
- </el-row>
- <el-row :gutter="20">
- <el-col :span="24" class="text-right" style="margin-top: 10px;">
- <el-button type="primary" @click="addRegion">
- 添加限购地区
- </el-button>
- </el-col>
- </el-row>
- </el-form>
- <div class="region-tip">注:可只选择到省或市层级</div>
- <div class="limited-region-list">
- <div class="list-title">已设置的限购地区</div>
- <div v-if="currentRuleRegions.length === 0" class="empty-tip">
- 暂未添加限购地区
- </div>
- <div
- v-for="(area, index) in currentRuleRegions"
- :key="index"
- class="limited-area-item"
- >
- <span>{{ formatRegionLabel(area) }}</span>
- <el-button
- type="danger"
- link
- @click="removeRegion(index)"
- >
- 删除
- </el-button>
- </div>
- </div>
- </div>
- </el-form-item>
- </el-form>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="ruleDialog.visible = false">取 消</el-button>
- <el-button type="primary" :loading="loading.saveRule" @click="submitRuleForm">
- 确 认
- </el-button>
- </span>
- </template>
- </el-dialog>
- </div>
- </template>
- <script>
- // 省级数据
- const PROVINCES = [
- {
- "code": "110000",
- "name": "北京市",
- "province": "11"
- },
- {
- "code": "120000",
- "name": "天津市",
- "province": "12"
- },
- {
- "code": "130000",
- "name": "河北省",
- "province": "13"
- },
- {
- "code": "140000",
- "name": "山西省",
- "province": "14"
- },
- {
- "code": "150000",
- "name": "内蒙古自治区",
- "province": "15"
- },
- {
- "code": "210000",
- "name": "辽宁省",
- "province": "21"
- },
- {
- "code": "220000",
- "name": "吉林省",
- "province": "22"
- },
- {
- "code": "230000",
- "name": "黑龙江省",
- "province": "23"
- },
- {
- "code": "310000",
- "name": "上海市",
- "province": "31"
- },
- {
- "code": "320000",
- "name": "江苏省",
- "province": "32"
- },
- {
- "code": "330000",
- "name": "浙江省",
- "province": "33"
- },
- {
- "code": "340000",
- "name": "安徽省",
- "province": "34"
- },
- {
- "code": "350000",
- "name": "福建省",
- "province": "35"
- },
- {
- "code": "360000",
- "name": "江西省",
- "province": "36"
- },
- {
- "code": "370000",
- "name": "山东省",
- "province": "37"
- },
- {
- "code": "410000",
- "name": "河南省",
- "province": "41"
- },
- {
- "code": "420000",
- "name": "湖北省",
- "province": "42"
- },
- {
- "code": "430000",
- "name": "湖南省",
- "province": "43"
- },
- {
- "code": "440000",
- "name": "广东省",
- "province": "44"
- },
- {
- "code": "450000",
- "name": "广西壮族自治区",
- "province": "45"
- },
- {
- "code": "460000",
- "name": "海南省",
- "province": "46"
- },
- {
- "code": "500000",
- "name": "重庆市",
- "province": "50"
- },
- {
- "code": "510000",
- "name": "四川省",
- "province": "51"
- },
- {
- "code": "520000",
- "name": "贵州省",
- "province": "52"
- },
- {
- "code": "530000",
- "name": "云南省",
- "province": "53"
- },
- {
- "code": "540000",
- "name": "西藏自治区",
- "province": "54"
- },
- {
- "code": "610000",
- "name": "陕西省",
- "province": "61"
- },
- {
- "code": "620000",
- "name": "甘肃省",
- "province": "62"
- },
- {
- "code": "630000",
- "name": "青海省",
- "province": "63"
- },
- {
- "code": "640000",
- "name": "宁夏回族自治区",
- "province": "64"
- },
- {
- "code": "650000",
- "name": "新疆维吾尔自治区",
- "province": "65"
- },
- {
- "code": "710000",
- "name": "台湾省",
- "province": "71"
- },
- {
- "code": "810000",
- "name": "香港特别行政区",
- "province": "81"
- },
- {
- "code": "820000",
- "name": "澳门特别行政区",
- "province": "82"
- }
- ];
- export default {
- name: 'LimitPurchaseRule',
- props: {
- axiosInstance: {
- type: Object,
- default: null
- }
- },
- data() {
- return {
- // 列表数据
- ruleList: [],
- loading: {
- list: false,
- saveRule: false
- },
-
- // 分页数据
- pagination: {
- page: 1,
- pageSize: 15,
- total: 0,
- hasMore: false
- },
- // 规则表单
- ruleDialog: {
- visible: false
- },
- ruleForm: {
- id: null,
- name: '',
- is_default: 0,
- status: 1
- },
- ruleRules: {
- name: [
- { required: true, message: '请输入规则名称', trigger: 'blur' }
- ]
- },
- // 使用本地省市区 JSON 级联
- provinces: PROVINCES,
- cities: [],
- areas: [],
- selectedProvince: '',
- selectedCity: '',
- selectedArea: '',
- currentRuleRegions: [], // 当前规则下的地区数组
-
- // 直辖市编码列表
- municipalityCodes: ['11', '12', '31', '50'] // 北京、天津、上海、重庆
- }
- },
- created() {
- this.fetchRuleList()
- },
- methods: {
- http() {
- // 默认用全局 axios,如果父组件传入则优先用父组件的
- return this.axiosInstance || this.$axios || this.$http
- },
-
- // 判断是否为直辖市
- isMunicipality(provinceCode) {
- if (!provinceCode) return false;
- // 提取前两位作为省份编码进行判断
- const provincePrefix = provinceCode.substring(0, 2);
- return this.municipalityCodes.includes(provincePrefix);
- },
-
- // 判断当前选中的省份是否为直辖市
- isMunicipalitySelected() {
- if (!this.selectedProvince) return false;
- const province = PROVINCES.find(p => p.name === this.selectedProvince);
- return province ? this.isMunicipality(province.code) : false;
- },
- // ========== 列表相关 ==========
- async fetchRuleList() {
- if (!this.http()) return
- this.loading.list = true
- try {
- const { page } = await this.http().get('/limit_purchase/rule', {
- params: {
- page: this.pagination.page,
- limit: this.pagination.pageSize
- }
- })
-
- // 根据分页数据结构处理
- if (page) {
- this.ruleList = Array.isArray(page.data) ? page.data : []
- this.pagination.total = page.total || 0
- this.pagination.hasMore = page.has_more || false
- } else {
- // 兼容旧的数据结构
- this.ruleList = []
- this.pagination.total = 0
- this.pagination.hasMore = false
- }
- } catch (e) {
- console.error(e)
- this.$message.error('加载规则列表失败')
- } finally {
- this.loading.list = false
- }
- },
-
- handlePageChange(page) {
- this.pagination.page = page
- this.fetchRuleList()
- },
- handleCreateRule() {
- this.ruleForm = {
- id: null,
- name: '',
- is_default: false,
- status: true
- }
- // 新建规则时清空当前规则地区
- this.currentRuleRegions = []
- this.selectedProvince = ''
- this.selectedCity = ''
- this.selectedArea = ''
- this.cities = []
- this.areas = []
- this.ruleDialog.visible = true
- this.$nextTick(() => {
- if (this.$refs.ruleFormRef && typeof this.$refs.ruleFormRef.clearValidate === 'function') {
- this.$refs.ruleFormRef.clearValidate()
- }
- })
- },
- handleEditRule(row) {
- this.ruleForm = {
- id: row.id,
- name: row.name,
- is_default: row.is_default || false,
- status: row.status
- }
- // 编辑规则时加载该规则已有的地区
- this.currentRuleRegions = Array.isArray(row.regions) ? [...row.regions] : []
- this.selectedProvince = ''
- this.selectedCity = ''
- this.selectedArea = ''
- this.cities = []
- this.areas = []
- this.ruleDialog.visible = true
- this.$nextTick(() => {
- if (this.$refs.ruleFormRef && typeof this.$refs.ruleFormRef.clearValidate === 'function') {
- this.$refs.ruleFormRef.clearValidate()
- }
- })
- },
- submitRuleForm() {
- this.$refs.ruleFormRef.validate(async (valid) => {
- if (!valid || !this.http()) return
- this.loading.saveRule = true
- try {
- if (this.ruleForm.id) {
- // 更新:名称 + 是否默认 + 状态 + 限购地区
- await this.http().put(`/limit_purchase/rule/${this.ruleForm.id}`, {
- name: this.ruleForm.name,
- is_default: this.ruleForm.is_default,
- status: this.ruleForm.status,
- regions: this.currentRuleRegions
- })
- this.$message.success('更新成功')
- } else {
- // 创建:名称 + 是否默认 + 状态 + 限购地区
- await this.http().post('/limit_purchase/rule', {
- name: this.ruleForm.name,
- is_default: this.ruleForm.is_default,
- status: this.ruleForm.status,
- regions: this.currentRuleRegions
- })
- this.$message.success('创建成功')
- }
- this.ruleDialog.visible = false
- this.fetchRuleList()
- } catch (e) {
- console.error(e)
- this.$message.error('保存失败')
- } finally {
- this.loading.saveRule = false
- }
- })
- },
- handleDeleteRule(row) {
- if (!this.http()) return
- this.$confirm(`确认删除规则「${row.name}」吗?`, '提示', {
- type: 'warning'
- })
- .then(async () => {
- try {
- await this.http().delete(`/limit_purchase/rule/${row.id}`)
- this.$message.success('删除成功')
- this.fetchRuleList()
- } catch (e) {
- console.error(e)
- this.$message.error('删除失败')
- }
- })
- .catch(() => {})
- },
- // 从网络接口获取城市数据
- async onProvinceChange() {
- this.selectedCity = ''
- this.selectedArea = ''
- this.areas = []
- this.cities = []
-
- if (!this.selectedProvince) return
-
- try {
- const province = PROVINCES.find(p => p.name === this.selectedProvince)
- if (province) {
- // 判断是否为直辖市
- if (this.isMunicipality(province.code)) {
- // 直辖市直接获取区县数据,参数需要特殊处理
- // 将110000转换为110100格式
- const municipalityCode = province.code.substring(0, 2) + '0100';
- const response = await this.http().get(`/limit_purchase/region/area/${municipalityCode}`)
- this.areas = response.data || []
- } else {
- // 非直辖市获取城市数据
- const response = await this.http().get(`/limit_purchase/region/city/${province.code}`)
- this.cities = response.data || []
- }
- }
- } catch (e) {
- console.error('获取城市数据失败:', e)
- this.$message.error('获取城市数据失败')
- }
- },
- // 从网络接口获取区县数据
- async onCityChange() {
- this.selectedArea = ''
- this.areas = []
-
- if (!this.selectedCity || !this.selectedProvince) return
-
- try {
- const province = PROVINCES.find(p => p.name === this.selectedProvince)
- if (province) {
- const city = this.cities.find(c => c.name === this.selectedCity)
- if (city) {
- const response = await this.http().get(`/limit_purchase/region/area/${city.code}`)
- this.areas = response.data || []
- }
- }
- } catch (e) {
- console.error('获取区县数据失败:', e)
- this.$message.error('获取区县数据失败')
- }
- },
- addRegion() {
- if (!this.selectedProvince) {
- this.$message.warning('请至少选择省份')
- return
- }
- // 查找选中的省市区对象
- const provinceObj = PROVINCES.find(p => p.name === this.selectedProvince)
- const cityObj = this.cities.find(c => c.name === this.selectedCity) || null
- const areaObj = this.areas.find(a => a.name === this.selectedArea) || null
- let region = {
- province_id: provinceObj ? provinceObj.code : null,
- province_name: this.selectedProvince,
- city_id: cityObj ? cityObj.code : null,
- city_name: this.selectedCity || null,
- district_id: areaObj ? areaObj.code : null,
- district_name: this.selectedArea || null
- };
- // 去重:检查是否已经存在相同或更广范围的地区
- const exists = this.currentRuleRegions.some(item => {
- // 完全匹配检查
- if (item.province_name !== region.province_name) return false;
-
- // 如果当前添加的是省级规则
- if (!region.city_name) {
- return !item.city_name; // 匹配已有省级规则
- }
-
- // 如果当前添加的是市级规则
- if (region.city_name && !region.district_name) {
- return item.city_name === region.city_name && !item.district_name;
- }
-
- // 如果当前添加的是区级规则
- if (region.city_name && region.district_name) {
- return item.city_name === region.city_name && item.district_name === region.district_name;
- }
-
- return false;
- });
- if (exists) {
- this.$message.warning('该地区已在列表中')
- return
- }
- this.currentRuleRegions.push(region)
-
- // 清空已选项
- this.selectedProvince = ''
- this.selectedCity = ''
- this.selectedArea = ''
- this.cities = []
- this.areas = []
- },
- removeRegion(index) {
- this.currentRuleRegions.splice(index, 1)
- },
- // ========== 工具方法 ==========
- formatRegionLabel(region) {
- const parts = []
- if (region.province_name) parts.push(region.province_name)
- if (region.city_name) parts.push(region.city_name)
- if (region.district_name) parts.push(region.district_name)
- return parts.join(' - ') || '未知地区'
- }
- }
- }
- </script>
- <style scoped>
- .limit-purchase-rule-container {
- padding: 0;
- }
- .rule-list-card {
- margin-bottom: 0;
- }
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .region-tag {
- margin-right: 4px;
- margin-bottom: 4px;
- }
- .text-muted {
- color: #999;
- }
- .region-selector-wrapper {
- padding: 10px 0 0;
- }
- .region-selector-title {
- display: flex;
- align-items: center;
- margin-bottom: 16px;
- font-size: 14px;
- }
- .region-selector-title .text-danger {
- color: #f56c6c;
- }
- .region-form {
- margin-bottom: 16px;
- }
- .limited-region-list {
- background: #fff7f7;
- border-radius: 4px;
- padding: 12px 16px;
- }
- .inner-region-form {
- margin-bottom: 8px;
- }
- .limited-region-list .list-title {
- font-size: 14px;
- margin-bottom: 8px;
- }
- .limited-region-list .empty-tip {
- font-size: 13px;
- color: #999;
- }
- .limited-area-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 8px 0;
- border-bottom: 1px solid #f5d1d1;
- }
- .limited-area-item:last-child {
- border-bottom: none;
- }
- .dialog-footer {
- text-align: right;
- }
- .text-right {
- text-align: right;
- }
- .form-tip {
- margin-left: 10px;
- font-size: 12px;
- color: #999;
- }
- .region-tip {
- font-size: 12px;
- color: #999;
- margin-top: 5px;
- margin-bottom: 15px;
- }
- </style>
|