Browse Source

选房页面

master-xiaowang
mk 2 months ago
parent
commit
592984224e
  1. 2
      .env.development
  2. 2
      .env.production
  3. 1
      components.d.ts
  4. 8
      index.html
  5. 1866
      package-lock.json
  6. 3
      package.json
  7. 2
      src/App.vue
  8. 47
      src/api/index.ts
  9. 138
      src/components/AMap.vue
  10. 39
      src/components/NavigationBar.vue
  11. 2
      src/i18n/modules/base/en.ts
  12. 5
      src/i18n/modules/base/zh-cn.ts
  13. 12
      src/main.ts
  14. 6
      src/router/index.ts
  15. 4
      src/utils/http.ts
  16. 36
      src/view/faq/index.vue
  17. 141
      src/view/showings/components/Agency.vue
  18. 100
      src/view/showings/components/HouseList.vue
  19. 69
      src/view/showings/index.vue
  20. 3254
      yarn.lock

2
.env.development

@ -4,3 +4,5 @@ VITE_ENV="开发环境"
VITE_BASE_URL="http://192.168.1.144:8080/mz-api" VITE_BASE_URL="http://192.168.1.144:8080/mz-api"
#每日一言服务 #每日一言服务
VITE_BASE_URL_YIYAN_SERVICE="https://tenapi.cn" VITE_BASE_URL_YIYAN_SERVICE="https://tenapi.cn"
VITE_AMAP_KEY = 8b6b7a05f40d067af88f6f211412984e
VITE_AMAP_SECURITY_KEY = 7439b95b4bb1850da7e5a1d65f1b8fc3

2
.env.production

@ -4,3 +4,5 @@ VITE_ENV="生产环境"
VITE_BASE_URL="http://219.146.91.110:30801/mz-api" VITE_BASE_URL="http://219.146.91.110:30801/mz-api"
#每日一言服务 #每日一言服务
VITE_BASE_URL_YIYAN_SERVICE="https://tenapi.cn" VITE_BASE_URL_YIYAN_SERVICE="https://tenapi.cn"
VITE_AMAP_KEY = 8b6b7a05f40d067af88f6f211412984e
VITE_AMAP_SECURITY_KEY = 7439b95b4bb1850da7e5a1d65f1b8fc3

1
components.d.ts

@ -7,6 +7,7 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AMap: typeof import('./src/components/AMap.vue')['default']
BottomNavigation: typeof import('./src/components/BottomNavigation.vue')['default'] BottomNavigation: typeof import('./src/components/BottomNavigation.vue')['default']
Faqs: typeof import('./src/components/Faqs.vue')['default'] Faqs: typeof import('./src/components/Faqs.vue')['default']
FooterBg: typeof import('./src/components/FooterBg.vue')['default'] FooterBg: typeof import('./src/components/FooterBg.vue')['default']

8
index.html

@ -11,3 +11,11 @@
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>
<style>
.amap-logo{
display: none !important;
}
.amap-copyright{
display: none!important;
}
</style>

1866
package-lock.json

File diff suppressed because it is too large

3
package.json

@ -9,7 +9,10 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.2", "axios": "^1.7.2",
"element-plus": "^2.10.2",
"pinia": "^2.2.2", "pinia": "^2.2.2",
"pinia-plugin-persistedstate": "^3.2.3", "pinia-plugin-persistedstate": "^3.2.3",
"postcss-pxtorem": "^6.1.0", "postcss-pxtorem": "^6.1.0",

2
src/App.vue

@ -8,5 +8,3 @@
<script setup lang="ts"> <script setup lang="ts">
</script> </script>
<style scoped></style>

47
src/api/index.ts

@ -0,0 +1,47 @@
import request from '@/utils/http.ts'
/**
*
* @param params deptName
*
* @returns
*/
export const getAgencyList = (params: object) => {
return request.get('/system/dept/listForH5', params)
}
/**
*
* @param params apartmentId
*
* @returns
*/
export const getHouseTypeList = (params: object) => {
return request.get('/asdh5/chooseRoom/getHouseTypeListForH5', params)
}
/**
*
* @param params apartmentId
*
* @returns
*/
export const getHouseTypeMap = (params: object) => {
return request.get('/mz/choose/list-house-type-with-count', params)
}
/**
*
* @param params apartmentId
*
* @returns
*/
export const getMapPoint = (params: object) => {
return request.get('/mz/choose/list-house-with-count', params)
}
/**
*
* @param params apartmentId
*
* @returns
*/
export const getDict = (params: object) => {
return request.get('/system/dict/data/getDict', params)
}

138
src/components/AMap.vue

@ -0,0 +1,138 @@
<template>
<div class="map-container" ref="mapContainer"></div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue"; // watch
import AMapLoader from "@amap/amap-jsapi-loader";
const props = defineProps({
center: {
type: Array,
default: () => [120.287882, 36.178786],
},
zoom: {
type: Number,
default: 10.5,
},
//
markers: {
type: Array,
default: () => [],
},
});
const mapContainer = ref(null);
let map = null;
let markers = []; // marker
//
const clearMarkers = () => {
if (map) {
map.remove(markers);
markers = [];
}
};
//
const emit = defineEmits(['marker-click']) //
const addMarkers = () => {
clearMarkers();
props.markers.forEach((marker, index) => {
const content = `
<div style="
background: ${marker.color || "#3388FF"};
color: white;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
padding: 8px;
border-radius: 15px;
white-space: nowrap;
">
${marker.name || ""}
<span style="
background: #ffffff;
color: ${marker.color || "#3388FF"};
border-radius: 10px;
min-width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 10px;
padding: 0 4px;
margin-left: 4px;
box-sizing: border-box;
font-weight: bold;
">${marker.total || ''}</span>
</div>
`;
const amarker = new AMap.Marker({
position: [marker.longitude, marker.latitude],
content: content,
offset: new AMap.Pixel(-15, -15),
});
//
amarker.on('click', () => {
emit('marker-click', {
marker,
index,
position: [marker.longitude, marker.latitude]
})
map.setCenter([marker.longitude, marker.latitude]);
map.setZoom(12);
});
markers.push(amarker);
map.add(amarker);
});
};
onMounted(async () => {
try {
const AMap = await AMapLoader.load({
key: import.meta.env.VITE_AMAP_KEY,
version: "2.0",
plugins: ["AMap.ToolBar", "AMap.Scale", "AMap.Marker"], // Marker
});
map = new AMap.Map(mapContainer.value, {
viewMode: "2D",
zoom: props.zoom,
center: props.center,
});
//
map.addControl(new AMap.ToolBar());
map.addControl(new AMap.Scale());
//
addMarkers();
// markers
watch(
() => props.markers,
() => {
addMarkers();
},
{ deep: true }
);
} catch (error) {
console.error("地图加载失败:", error);
}
});
</script>
<style scoped>
.map-container {
width: 100%;
height: 100vh;
min-height: 400px;
}
</style>

39
src/components/NavigationBar.vue

@ -8,8 +8,8 @@
class="text-[16px] font-medium hidden md:block hover:cursor-pointer text-white"> class="text-[16px] font-medium hidden md:block hover:cursor-pointer text-white">
<ul class="flex justify-evenly"> <ul class="flex justify-evenly">
<template v-for="(item, index) in navList" :key="index"> <template v-for="(item, index) in navList" :key="index">
<!-- item, 1 --> <!-- -->
<li class="mr-[48px] nav-item" :data="index" @click="handleMenuClick()" <li class="mr-[48px] nav-item" :data="index" @click="handleMenuClick( item, 1)"
:class="currentKey === index ? 'nav-item-active nav-item-active-text ' : ''"> :class="currentKey === index ? 'nav-item-active nav-item-active-text ' : ''">
{{ $t(`base.nav.${item.name}`) }} {{ $t(`base.nav.${item.name}`) }}
</li> </li>
@ -61,9 +61,9 @@
</template> </template>
<div> <div>
<ul class="flex justify-evenly flex-col"> <ul class="flex justify-evenly flex-col">
<!-- item, 2 --> <!-- -->
<template v-for="(item, index) in navList" :key="index"> <template v-for="(item, index) in navList" :key="index">
<li class="mt-[48px] nav-item ml-1 h-[24px] leading-[24px]" :data="index" @click="handleMenuClick()" <li class="mt-[48px] nav-item ml-1 h-[24px] leading-[24px]" :data="index" @click="handleMenuClick(item, 2)"
:class="currentKey === index ? 'nav-item-active-h5 nav-item-active-text ' : ''"> :class="currentKey === index ? 'nav-item-active-h5 nav-item-active-text ' : ''">
{{ $t(`base.nav.${item.name}`) }} {{ $t(`base.nav.${item.name}`) }}
</li> </li>
@ -120,14 +120,15 @@ const navList = reactive([
}, },
{ {
id: 2, id: 2,
name: 'About', name: 'Showings',
path: '/layout/about', path: '/layout/showings',
}, },
{ {
id: 3, id: 3,
name: 'FAQ', name: 'About',
path: '/layout/faq', path: '/layout/about',
}, },
]) ])
const visible = ref(false) const visible = ref(false)
const handleShowMenu = () => { const handleShowMenu = () => {
@ -140,17 +141,17 @@ const handleOk = () => {
const handleCancel = () => { const handleCancel = () => {
visible.value = false visible.value = false
} }
//nav e: any, type: number //nav
const handleMenuClick = () => { const handleMenuClick = ( e: any, type: number) => {
Message.info('功能开发中') // Message.info('')
// if (type === 2) { if (type === 2) {
// currentKey.value = e.id currentKey.value = e.id
// visible.value = false visible.value = false
// route.push(e.path) route.push(e.path)
// } else { } else {
// currentKey.value = e.id currentKey.value = e.id
// route.push(e.path) route.push(e.path)
// } }
} }
// //
const handleLogin = () => { const handleLogin = () => {

2
src/i18n/modules/base/en.ts

@ -3,7 +3,7 @@ export default {
Home: 'Home', Home: 'Home',
Policy: 'Policy', Policy: 'Policy',
About: 'About', About: 'About',
FAQ: 'FAQ', Showings: 'Showings',
Contact: 'Contact', Contact: 'Contact',
}, },
languageSwitcher: { languageSwitcher: {

5
src/i18n/modules/base/zh-cn.ts

@ -2,9 +2,8 @@ export default {
nav: { nav: {
Home: '首页', Home: '首页',
Policy: '政策资讯', Policy: '政策资讯',
About: '看房选房', About: '续期申请',
FAQ: '续期申请', Showings: '看房选房',
}, },
languageSwitcher: { languageSwitcher: {
title: '语言', title: '语言',

12
src/main.ts

@ -4,7 +4,17 @@ import router from './router/routerBeforeEach'
import App from './App.vue' import App from './App.vue'
import i18n from './i18n/index.ts' import i18n from './i18n/index.ts'
import { setupStore } from './stores' import { setupStore } from './stores'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 高德地图安全配置
window._AMapSecurityConfig = {
securityJsCode: import.meta.env.VITE_AMAP_SECURITY_KEY // 使用vite的环境变量方式
}
const app = createApp(App) const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
setupStore(app) setupStore(app)
app.use(router).use(i18n).mount('#app') app.use(router).use(i18n).use(ElementPlus).mount('#app')

6
src/router/index.ts

@ -30,9 +30,9 @@ const routes: Array<RouteRecordRaw> = [
meta: { title: 'about' }, meta: { title: 'about' },
}, },
{ {
path: 'faq', path: 'Showings',
component: () => import('@/view/faq/index.vue'), component: () => import('@/view/showings/index.vue'),
meta: { title: 'faq' }, meta: { title: 'showings' },
}, },
{ {
path: 'contact', path: 'contact',

4
src/utils/http.ts

@ -58,8 +58,6 @@ class RequestHttp {
this.service.interceptors.response.use( this.service.interceptors.response.use(
(response: AxiosResponse) => { (response: AxiosResponse) => {
const { data, config } = response // 解构 const { data, config } = response // 解构
console.log(config)
if (data.code === RequestEnums.OVERDUE) { if (data.code === RequestEnums.OVERDUE) {
// 登录信息失效,应跳转到登录页面,并清空本地的token // 登录信息失效,应跳转到登录页面,并清空本地的token
localStorage.setItem('token', '') localStorage.setItem('token', '')
@ -67,7 +65,7 @@ class RequestHttp {
} }
// 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错) // 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错)
if (data.code && data.code !== RequestEnums.SUCCESS) { if (data.code && data.code !== RequestEnums.SUCCESS) {
Message.error(data) // 此处也可以使用组件提示报错信息 Message.error(data.msg) // 此处也可以使用组件提示报错信息
return Promise.reject(data) return Promise.reject(data)
} }
return data return data

36
src/view/faq/index.vue

@ -1,36 +0,0 @@
<template>
<div class="bg-syGreyBg overflow-hidden">
<div class="md:w-[1200px] m-auto md:mt-[66px] w-[390px] mt-2">
<div class="w-[350px] m-auto md:w-[1200px]">
<div
class="md:w-[86px] mt-8 md:h-[50px] w-[75px] h-[38px] rounded-3xl border border-gray-200 flex justify-center items-center">
<span class="text-secondary md:text-[18px] text-[14px] font-normal">FAQs</span>
</div>
<div
class="mt-4 md:w-[629px] md:h-[54px] md:text-5xl font-medium md:whitespace-nowrap w-[350px] h-[80px] text-3xl">
Frequently Asked Questions
</div>
<div class="mt-4 w-full md:text-lg font-normal text-base">
We understand that you may have some questions about insurance. We have compiled a list of frequently asked
questions to help you get the information you need. If you have any other questions, please do not hesitate to
contact us.
</div>
</div>
<Faqs :type="2" />
</div>
<!-- footer背景 -->
<FooterBg />
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { getCardList } from '@/api/test'
const store = useUserStore()
const getInfo = async () => {
const res = await getCardList()
console.log(res, store)
}
getInfo()
</script>
<style scoped></style>

141
src/view/showings/components/Agency.vue

@ -0,0 +1,141 @@
<template>
<div class="flex items-center gap-5">
<el-dropdown trigger="click">
<span class="el-dropdown-link">
{{ cityName }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="handleCity(item.deptId, item.deptName)"
v-for="(item, index) in cityList"
:label="item.deptName"
:value="item.deptId"
:key="index"
>{{ item.deptName }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click">
<span class="el-dropdown-link">
{{ apartmentName
}}<el-icon class="el-icon--right"><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(item, index) in apartmentList"
:label="item.deptName"
:value="item.deptId"
:key="index"
@click="handleApartment(item.deptId, item.deptName)"
>{{ item.deptName }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown trigger="click">
<span class="el-dropdown-link">
{{ houseTypeName
}}<el-icon class="el-icon--right"><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(item, index) in roomTypeList"
:label="item.typeName"
:value="item.id"
:key="index"
@click="handleHouseType(item.id, item.typeName)"
>{{ item.typeName }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from "vue";
import { getAgencyList, getHouseTypeList } from "@/api/index";
const emit = defineEmits(["changeAngecy"]);
const props = defineProps({
deptName: {
//
type: String, //
default: "",
},
});
const list = ref([]); //
const joinIn = reactive({
//
deptName: props.deptName || "青岛市",
});
const deptId = ref("");
const cityList = ref([]); //
const apartmentList = ref([]); //
const roomTypeList = ref([]); //
const cityName = ref("请选择"); //
const apartmentName = ref("请选择"); //
const houseTypeName = ref("请选择"); //
const getQIngDaoId = async () => {
//
try {
const res = await getAgencyList(joinIn); //
list.value = res.data; //
deptId.value = list.value[0].deptId;
getDropdownList("city");
} catch (err) {
console.error("获取数据失败:", err); //
}
};
const getDropdownList = async (type) => {
//
try {
const res = await getAgencyList({ parentId: deptId.value }); //
if (type === "city") {
cityList.value = res.data; //
console.log(cityList.value);
} else if (type === "apartment") {
apartmentList.value = res.data; //
}
} catch (err) {
console.error("获取数据失败:", err); //
}
};
const handleCity = (id, name) => {
deptId.value = id;
cityName.value = name;
apartmentList.value = []; // apartmentList
roomTypeList.value = []; // roomTypeList
apartmentName.value = "请选择"; // apartmentName
houseTypeName.value = "请选择"; // apartmentName
emit("changeAngecy", { id, type: "city" });
getDropdownList("apartment");
};
const handleApartment = (id, name) => {
roomTypeList.value = []; // roomTypeList
apartmentName.value = name;
deptId.value = id;
houseTypeName.value = "请选择"; // apartmentName
emit("changeAngecy", { id, type: "apartment" });
getHouseType(id);
};
const handleHouseType = (id, name) => {
houseTypeName.value = name;
deptId.value = id;
emit("changeAngecy", { id, type: "houseType" });
};
const getHouseType = async (id) => {
try {
const res = await getHouseTypeList({ apartmentId: id }); //
roomTypeList.value = res.data; //
} catch (err) {
console.error("获取数据失败:", err); //
}
};
onMounted(() => {
//
getQIngDaoId(); //
});
</script>

100
src/view/showings/components/HouseList.vue

@ -0,0 +1,100 @@
<template>
<div class="w-[420px] h-[344px] overflow-y-auto">
<div>选择房型</div>
<el-divider />
<div class="flex flex-col">
<div class="flex gap-5" v-for="(item, index) in list" :key="index">
<img :src="item.coverImg" alt="" class="w-[140px] h-[100px]" />
<div class="flex-1 flex flex-col">
<div class="font-bold">{{ item.name }}</div>
<div>{{ item.total }}</div>
<div class="text-gray-400">{{ item.address }}</div>
<div>
<div
v-for="tag in item.labels"
:key="tag"
class="mr-1 mb-1 inline-flex items-center px-2 py-1 rounded text-xs font-medium"
:class="tag === '1' ? 'bule' : tag === '2' ? 'green' : 'yellow'"
>
{{ getLabel(tag) }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted, watch } from "vue";
import { getHouseTypeMap, getDict } from "@/api/index";
const props = defineProps({
selfObj: {
type: Object,
default: () => {
return {};
},
},
}); // props
const list = ref([]);
const dict = ref({});
const joinIn = reactive({
pageNum: 1,
pageSize: 200,
regionCode: "", //
apartmentId: "", //
houseTypeId: "", //
});
const getDictList = async () => {
const res = await getDict({ dictType: "apartment_label" });
try {
dict.value = res.rows[0];
} catch (error) {}
};
const getList = async () => {
const res = await getHouseTypeMap(joinIn);
list.value = res.rows;
};
getDictList();
watch(
() => props.selfObj,
(newVal, oldVal) => {
if (newVal.type === "city") {
joinIn.regionCode = newVal.id;
} else if (newVal.type === "apartment") {
joinIn.apartmentId = newVal.id;
} else if (newVal.type === "houseType") {
joinIn.houseTypeId = newVal.id;
}
getList();
},
{ deep: true }
);
getList();
const getLabel = (tag) => {
// dict.value
const dictArray = Array.isArray(dict.value)
? dict.value
: Object.values(dict.value);
return dictArray.find((item) => item.dictValue === tag)?.dictLabel || tag;
};
</script>
<style scoped>
.bule{
color: #99bbfe;
border:1px solid #99bbfe;
padding: 0 5px;
}
.green{
color: #3bcccd;
border:1px solid #3bcccd;
padding: 0 5px;
}
.yellow{
color: #efca3f;
border:1px solid #efca3f;
padding: 0 5px;
}
</style>

69
src/view/showings/index.vue

@ -0,0 +1,69 @@
<template>
<div class="bg-syGreyBg overflow-hidden">
<div class="bg-white absolute top-20 left-24 z-20 py-4 px-6 rounded-md">
<Agency :deptName="deptName" @changeAngecy="changeAngecy" />
</div>
<Amap :markers="markers" @marker-click="handleMarkerClick" />
<div class="bg-white absolute bottom-32 right-24 z-20 py-4 px-6 rounded-md">
<HouseList :selfObj="selfObj" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onUnmounted } from "vue";
import Amap from "@/components/AMap.vue";
import Agency from "./components/Agency.vue";
import HouseList from "./components/HouseList.vue";
import { getMapPoint } from "@/api/index";
const deptName = ref("");
let selfObj = ref({});
//
const markers = ref([]);
const deptId = ref("");
const handleMarkerClick = (event) => {
console.log("点击了标记点:", event);
selfObj.value = { type: "apartment", id: event.marker.id };
//
markers.value = markers.value.map((marker) => ({
...marker,
color: "#3388FF", //
}));
//
markers.value[event.index].color = "#ff9f00";
};
const getMarkers = async (event) => {
try {
let pamrs = {
pageSize: 200,
pageNum: 1,
regionCode: '', //
apartmentId: "", //
houseTypeId: "", //
};
if (event) {
if (event.type === "city") {
pamrs.regionCode = event.id;
} else if (event.type === "apartment") {
pamrs.apartmentId = event.id;
} else if (event.type === "houseType") {
pamrs.houseTypeId = event.id;
}
}
const res = await getMapPoint(pamrs);
markers.value = res.rows;
console.log(markers, "seeeee");
} catch (error) {
console.log(error);
}
};
const changeAngecy = (event) => {
selfObj.value = { type: event.type, id: event.id };
getMarkers(event);
};
getMarkers();
</script>
<style scoped></style>

3254
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save