预定义组件备份

This commit is contained in:
丁杰 2026-06-09 17:49:15 +08:00
parent d4a6a63b65
commit a7f9544bc5
13 changed files with 2888 additions and 2021 deletions

View File

@ -0,0 +1,97 @@
<template>
<view class="form-checkbox">
<view v-for="(item, index) in options" :key="index" class="checkbox-item" @click="toggle(item)">
<view :class="['checkbox-dot', { checked: isSelected(item.value) }]">
<text v-if="isSelected(item.value)" class="check-icon">&#10003;</text>
</view>
<text class="checkbox-label">{{ item.label }}</text>
</view>
</view>
</template>
<script>
export default {
name: 'FormItemCheckbox',
props: {
value: {
type: Array,
default: function () {
return [];
}
},
options: {
type: Array,
default: function () {
return [];
}
}
},
data: function () {
return {
selectedValues: []
};
},
watch: {
value: {
handler: function (val) {
this.selectedValues = Array.isArray(val) ? val.slice() : [];
},
immediate: true
}
},
methods: {
isSelected: function (val) {
return this.selectedValues.indexOf(val) > -1;
},
toggle: function (item) {
var index = this.selectedValues.indexOf(item.value);
if (index > -1) {
this.selectedValues.splice(index, 1);
} else {
this.selectedValues.push(item.value);
}
this.$emit('input', this.selectedValues.slice());
}
}
};
</script>
<style scoped>
.form-checkbox {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
}
.checkbox-item {
display: flex;
align-items: center;
padding: 16rpx 0;
}
.checkbox-dot {
width: 36rpx;
height: 36rpx;
border-radius: 6rpx;
border: 2rpx solid #dddddd;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
}
.checkbox-dot.checked {
border-color: #e8101e;
background-color: #e8101e;
}
.check-icon {
color: #ffffff;
font-size: 24rpx;
}
.checkbox-label {
font-size: 28rpx;
color: #333333;
}
</style>

View File

@ -0,0 +1,112 @@
<template>
<view class="form-image">
<view class="image-list">
<view v-for="(item, index) in imageList" :key="index" class="image-item">
<image :src="item" mode="aspectFill" class="preview-image" @click="previewImage(index)" />
<view class="delete-btn" @click.stop="deleteImage(index)">
<text class="delete-icon">&times;</text>
</view>
</view>
<view v-if="imageList.length < maxCount" class="image-item add-btn" @click="chooseImage">
<text class="add-icon">+</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: "FormItemImage",
props: {
value: { type: Array, default: function() { return []; } },
maxCount: { type: Number, default: 9 }
},
data: function() {
return {
imageList: []
};
},
watch: {
value: {
handler: function(val) {
this.imageList = Array.isArray(val) ? val.slice() : [];
},
immediate: true
}
},
methods: {
chooseImage: function() {
var remaining = this.maxCount - this.imageList.length;
if (remaining <= 0) return;
var self = this;
uni.chooseImage({
count: remaining,
sizeType: ["compressed"],
sourceType: ["album", "camera"],
success: function(res) {
self.imageList = self.imageList.concat(res.tempFilePaths);
self.$emit("input", self.imageList);
}
});
},
deleteImage: function(index) {
this.imageList.splice(index, 1);
this.$emit("input", this.imageList);
},
previewImage: function(index) {
uni.previewImage({
urls: this.imageList,
current: index
});
}
}
};
</script>
<style scoped>
.form-image {
width: 100%;
}
.image-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.image-item {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
overflow: hidden;
position: relative;
}
.preview-image {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
top: 0;
right: 0;
width: 40rpx;
height: 40rpx;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #ffffff;
font-size: 28rpx;
}
.add-btn {
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx dashed #cccccc;
}
.add-icon {
font-size: 60rpx;
color: #999999;
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<input type="number" class="form-input" :value="value" :placeholder="placeholder" placeholder-class="placeholder"
@input="$emit('input', $event.detail.value)" />
</template>
<script>
export default {
name: 'FormItemNumber',
props: {
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: String,
default: '请输入数字'
}
}
};
</script>
<style scoped>
.form-input {
flex: 1;
height: 80rpx;
font-size: 28rpx;
color: #333333;
text-align: center;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 0 24rpx;
}
.placeholder {
font-weight: 400;
font-size: 28rpx;
color: #999999;
}
</style>

View File

@ -0,0 +1,71 @@
<template>
<view class="form-radio">
<view v-for="(item, index) in options" :key="index" class="radio-item" @click="onSelect(item.value)">
<view class="radio-dot" :class="{ active: value == item.value }"></view>
<text class="radio-label">{{ item.label }}</text>
</view>
</view>
</template>
<script>
export default {
name: "FormItemRadio",
props: {
value: {
type: [String, Number],
default: ""
},
options: {
type: Array,
default: function() {
return [];
}
}
},
methods: {
onSelect: function(val) {
this.$emit("input", val);
}
}
};
</script>
<style scoped>
.form-radio {
display: flex;
flex-wrap: wrap;
}
.radio-item {
display: flex;
align-items: center;
margin-right: 40rpx;
margin-bottom: 16rpx;
}
.radio-dot {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #cccccc;
margin-right: 12rpx;
position: relative;
}
.radio-dot.active {
border-color: #e8101e;
background-color: #e8101e;
}
.radio-dot.active::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background-color: #ffffff;
}
.radio-label {
font-size: 28rpx;
color: #333333;
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<view class="form-richtext">
<view class="richtext-toolbar">
<text class="toolbar-btn" @click="insertTag('b')">加粗</text>
<text class="toolbar-btn" @click="insertTag('i')">斜体</text>
<text class="toolbar-btn" @click="insertImage">图片</text>
</view>
<textarea class="richtext-editor" :value="value" placeholder="请输入图文内容" placeholder-class="placeholder"
@input="$emit('input', $event.detail.value)" />
<view v-if="value" class="richtext-preview">
<rich-text :nodes="value" />
</view>
</view>
</template>
<script>
export default {
name: "FormItemRichtext",
props: {
value: { type: String, default: "" },
},
methods: {
insertTag(tag) {
const text = `<${tag}></${tag}>`;
this.$emit("input", (this.value || "") + text);
},
insertImage() {
uni.chooseImage({
count: 1,
success: (res) => {
const imgTag = `<img src="${res.tempFilePaths[0]}" />`;
this.$emit("input", (this.value || "") + imgTag);
},
});
},
},
};
</script>
<style scoped>
.form-richtext {
width: 100%;
}
.richtext-toolbar {
display: flex;
gap: 16rpx;
padding: 16rpx;
background-color: #f0f0f0;
border-radius: 8rpx 8rpx 0 0;
}
.toolbar-btn {
padding: 8rpx 16rpx;
background-color: #ffffff;
border-radius: 4rpx;
font-size: 24rpx;
color: #333333;
}
.richtext-editor {
width: 100%;
height: 300rpx;
background-color: #ffffff;
border: 2rpx solid #e0e0e0;
border-top: none;
border-radius: 0 0 8rpx 8rpx;
padding: 20rpx;
font-size: 28rpx;
color: #333333;
}
.placeholder {
color: #999999;
}
.richtext-preview {
margin-top: 16rpx;
padding: 16rpx;
background-color: #f9f9f9;
border-radius: 8rpx;
}
</style>

View File

@ -0,0 +1,79 @@
<template>
<picker :range="optionLabels" @change="onChange">
<view class="form-select">
<text :class="['select-text', selectedLabel ? '' : 'placeholder']">
{{ selectedLabel || placeholder }}
</text>
<image src="https://mrrplus.oss-cn-beijing.aliyuncs.com/wxstaic/images/arrow_right.png" class="arrow-right" mode="aspectFit" />
</view>
</picker>
</template>
<script>
export default {
name: "FormItemSelect",
props: {
value: {
type: [String, Number],
default: ""
},
options: {
type: Array,
default: function() {
return [];
}
},
placeholder: {
type: String,
default: "请选择"
}
},
computed: {
optionLabels: function() {
return this.options.map(function(item) {
return item.label;
});
},
selectedLabel: function() {
var self = this;
var found = this.options.find(function(item) {
return item.value == self.value;
});
return found ? found.label : "";
}
},
methods: {
onChange: function(e) {
var index = e.detail.value;
var selected = this.options[index];
if (selected) {
this.$emit("input", selected.value);
}
}
}
};
</script>
<style scoped>
.form-select {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 80rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 0 24rpx;
}
.select-text {
font-size: 28rpx;
color: #333333;
}
.placeholder {
color: #999999;
}
.arrow-right {
width: 24rpx;
height: 24rpx;
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<input type="text" class="form-input" :value="value" :placeholder="placeholder" placeholder-class="placeholder"
@input="$emit('input', $event.detail.value)" />
</template>
<script>
export default {
name: 'FormItemText',
props: {
value: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入'
}
}
};
</script>
<style scoped>
.form-input {
flex: 1;
height: 80rpx;
font-size: 28rpx;
color: #333333;
text-align: center;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 0 24rpx;
}
.placeholder {
font-weight: 400;
font-size: 28rpx;
color: #999999;
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<view class="textarea-wrap">
<textarea class="form-textarea" :value="value" :placeholder="placeholder" placeholder-class="placeholder"
maxlength="200" @input="$emit('input', $event.detail.value)" />
<text class="char-count">{{ (value || '').length }}/200</text>
</view>
</template>
<script>
export default {
name: 'FormItemTextarea',
props: {
value: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入'
}
}
};
</script>
<style scoped>
.textarea-wrap {
width: 100%;
position: relative;
}
.form-textarea {
width: 100%;
height: 150rpx;
font-size: 28rpx;
color: #333333;
line-height: 1.5;
background-color: #f5f5f5;
border-radius: 8rpx;
padding: 20rpx;
box-sizing: border-box;
}
.placeholder {
font-weight: 400;
font-size: 28rpx;
color: #999999;
}
.char-count {
position: absolute;
right: 16rpx;
bottom: 16rpx;
font-size: 24rpx;
color: #999999;
}
</style>

View File

@ -0,0 +1,104 @@
<template>
<view class="form-video">
<view v-if="videoUrl" class="video-preview">
<video :src="videoUrl" class="video-player" controls />
<view class="delete-btn" @click="deleteVideo">
<text class="delete-icon">&times;</text>
</view>
</view>
<view v-else class="video-add" @click="chooseVideo">
<text class="add-icon">+</text>
<text class="add-text">选择视频</text>
</view>
</view>
</template>
<script>
export default {
name: "FormItemVideo",
props: {
value: { type: String, default: "" }
},
data: function() {
return {
videoUrl: ""
};
},
watch: {
value: {
handler: function(val) {
this.videoUrl = val || "";
},
immediate: true
}
},
methods: {
chooseVideo: function() {
var self = this;
uni.chooseVideo({
sourceType: ["album", "camera"],
maxDuration: 60,
success: function(res) {
self.videoUrl = res.tempFilePath;
self.$emit("input", self.videoUrl);
}
});
},
deleteVideo: function() {
this.videoUrl = "";
this.$emit("input", "");
}
}
};
</script>
<style scoped>
.form-video {
width: 100%;
}
.video-preview {
width: 100%;
position: relative;
}
.video-player {
width: 100%;
height: 400rpx;
border-radius: 8rpx;
}
.delete-btn {
position: absolute;
top: 16rpx;
right: 16rpx;
width: 48rpx;
height: 48rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #ffffff;
font-size: 32rpx;
}
.video-add {
width: 300rpx;
height: 300rpx;
background-color: #f5f5f5;
border: 2rpx dashed #cccccc;
border-radius: 8rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.add-icon {
font-size: 60rpx;
color: #999999;
}
.add-text {
font-size: 24rpx;
color: #999999;
margin-top: 8rpx;
}
</style>

View File

@ -0,0 +1,197 @@
<template>
<view class="dynamic-form">
<view v-for="(field, index) in fields" :key="field.id || index" class="form-item"
:class="{ 'form-item-up': isUpType(field.type) }">
<view class="form-header">
<text class="form-label" :class="{ required: field.is_required == 1 }">{{ field.label }}</text>
<text v-if="field.tips" class="form-tips">{{ field.tips }}</text>
</view>
<view class="form-control">
<!-- 单行文本 -->
<form-item-text v-if="field.type === 'text'" :value="formData[field.key]"
:placeholder="field.placeholder" @input="onInput(field.key, $event)" />
<!-- 多行文本 -->
<form-item-textarea v-else-if="field.type === 'textarea'" :value="formData[field.key]"
:placeholder="field.placeholder" @input="onInput(field.key, $event)" />
<!-- 数字输入 -->
<form-item-number v-else-if="field.type === 'number'" :value="formData[field.key]"
:placeholder="field.placeholder" @input="onInput(field.key, $event)" />
<!-- 多图上传 -->
<form-item-image v-else-if="field.type === 'image_multi'" :value="formData[field.key]"
:maxCount="field.maxCount || 9" @input="onInput(field.key, $event)" />
<!-- 视频上传 -->
<form-item-video v-else-if="field.type === 'video'" :value="formData[field.key]"
@input="onInput(field.key, $event)" />
<!-- 下拉选择 -->
<form-item-select v-else-if="field.type === 'select'" :value="formData[field.key]"
:options="field.options" :placeholder="field.placeholder" @input="onInput(field.key, $event)" />
<!-- 单选 -->
<form-item-radio v-else-if="field.type === 'single_select'" :value="formData[field.key]"
:options="field.options" @input="onInput(field.key, $event)" />
<!-- 多选 -->
<form-item-checkbox v-else-if="field.type === 'multi_select'" :value="formData[field.key]"
:options="field.options" @input="onInput(field.key, $event)" />
<!-- 图文编辑器 -->
<form-item-richtext v-else-if="field.type === 'richtext_editor'" :value="formData[field.key]"
@input="onInput(field.key, $event)" />
</view>
</view>
</view>
</template>
<script>
import FormItemText from './FormItemText.vue';
import FormItemTextarea from './FormItemTextarea.vue';
import FormItemNumber from './FormItemNumber.vue';
import FormItemImage from './FormItemImage.vue';
import FormItemVideo from './FormItemVideo.vue';
import FormItemSelect from './FormItemSelect.vue';
import FormItemRadio from './FormItemRadio.vue';
import FormItemCheckbox from './FormItemCheckbox.vue';
import FormItemRichtext from './FormItemRichtext.vue';
export default {
name: 'DynamicForm',
components: {
FormItemText,
FormItemTextarea,
FormItemNumber,
FormItemImage,
FormItemVideo,
FormItemSelect,
FormItemRadio,
FormItemCheckbox,
FormItemRichtext
},
props: {
fields: {
type: Array,
default: function () {
return [];
}
},
value: {
type: Object,
default: function () {
return {};
}
}
},
data: function () {
return {
formData: {}
};
},
watch: {
value: {
handler: function (val) {
this.formData = Object.assign({}, val);
},
immediate: true,
deep: true
},
fields: {
handler: function (val) {
var self = this;
if (val && val.length > 0) {
val.forEach(function (field) {
if (self.formData[field.key] === undefined) {
self.$set(self.formData, field.key, self.getDefaultValue(field.type));
}
});
}
},
immediate: true
}
},
methods: {
isUpType: function (type) {
return type === 'image_multi' || type === 'video';
},
getDefaultValue: function (type) {
if (type === 'multi_select') {
return [];
}
if (type === 'image_multi') {
return [];
}
return '';
},
onInput: function (key, val) {
this.$set(this.formData, key, val);
this.$emit('input', Object.assign({}, this.formData));
},
validate: function () {
for (var i = 0; i < this.fields.length; i++) {
var field = this.fields[i];
if (field.is_required == 1) {
var val = this.formData[field.key];
if (val === undefined || val === null || val === '' || (Array.isArray(val) && val.length === 0)) {
uni.showToast({
title: '请填写' + field.label,
icon: 'none'
});
return false;
}
}
}
return true;
},
getData: function () {
return Object.assign({}, this.formData);
}
}
};
</script>
<style scoped>
.dynamic-form {
width: 100%;
}
.form-item {
background-color: #fff;
padding: 24rpx;
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
border-radius: 10rpx;
margin-bottom: 24rpx;
}
.form-item-up {
flex-flow: column nowrap;
justify-content: space-between;
align-items: flex-start;
}
.form-header {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.form-label {
font-size: 28rpx;
color: #333333;
}
.form-label.required::before {
content: '*';
color: #e8101e;
margin-right: 4rpx;
}
.form-tips {
font-size: 24rpx;
color: #999999;
margin-left: 10rpx;
}
.form-control {
width: 100%;
flex: 1;
}
</style>

View File

@ -100,12 +100,54 @@
</view>
<!-- <view class="add-btn"></view> -->
<!-- 服务分类选择弹窗 -->
<view class="category-popup-mask" v-if="showCategoryPopup" @click="closeCategoryPopup">
<view class="category-popup" @click.stop>
<view class="category-popup-header">
<text class="category-popup-title">选择服务分类</text>
<view class="category-popup-close" @click="closeCategoryPopup">
<text class="category-popup-close-icon">&times;</text>
</view>
</view>
<view class="category-popup-body">
<!-- 左侧一级分类 -->
<scroll-view scroll-y class="category-left">
<view v-for="(item, index) in firstClassList" :key="item.id"
:class="['category-left-item', { active: selectedFirstIndex === index }]"
@click="selectFirstClass(index, item)">
<text class="category-left-text">{{ item.title }}</text>
</view>
</scroll-view>
<!-- 右侧二级分类 -->
<scroll-view scroll-y class="category-right">
<view v-if="secondClassList.length === 0" class="category-empty">
<text class="category-empty-text">暂无子分类</text>
</view>
<view v-for="item in secondClassList" :key="item.id" class="category-right-item"
@click="selectSecondClass(item)">
<text class="category-right-text">{{ item.title }}</text>
<view :class="['category-dot', { selected: selectedSecondId === item.id }]"></view>
</view>
</scroll-view>
</view>
<!-- 底部按钮 -->
<view class="category-popup-footer">
<view class="category-popup-btn category-popup-btn-cancel" @click="closeCategoryPopup">
<text>取消</text>
</view>
<view class="category-popup-btn category-popup-btn-confirm" @click="confirmCategory">
<text>确认</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import request from "../../utils/request";
export default {
import request from "../../utils/request";
export default {
data() {
return {
title: "",
@ -137,6 +179,12 @@
startX: 0,
moveX: 0,
currentIndex: -1,
//
showCategoryPopup: false,
firstClassList: [],
secondClassList: [],
selectedFirstIndex: -1,
selectedSecondId: null,
};
},
// computed: {
@ -248,9 +296,72 @@
},
addService() {
uni.navigateTo({
url: `/pages/shop/add-service`,
//
request.post("/sj/firstclass", {}).then((res) => {
if (res.data && res.data.length > 0) {
this.firstClassList = res.data;
this.selectedFirstIndex = 0;
this.selectedSecondId = null;
this.secondClassList = [];
this.showCategoryPopup = true;
//
this.loadSecondClass(res.data[0].id);
} else {
uni.showToast({
title: "暂无分类数据",
icon: "none",
});
}
});
},
//
loadSecondClass(firstId) {
request.post("/user/secondclass", {
id: firstId,
}).then((res) => {
this.secondClassList = res.data || [];
});
},
//
selectFirstClass(index, item) {
if (this.selectedFirstIndex === index) return;
this.selectedFirstIndex = index;
this.selectedSecondId = null;
this.loadSecondClass(item.id);
},
//
selectSecondClass(item) {
this.selectedSecondId = item.id;
},
//
confirmCategory() {
if (this.selectedFirstIndex < 0) {
uni.showToast({
title: "请选择服务分类",
icon: "none",
});
return;
}
//
if (this.secondClassList.length > 0 && !this.selectedSecondId) {
uni.showToast({
title: "请选择服务子类",
icon: "none",
});
return;
}
const firstItem = this.firstClassList[this.selectedFirstIndex];
const url = `/pages/shop/add-service?first_class_id=${firstItem.id}`;
uni.navigateTo({ url });
this.closeCategoryPopup();
},
//
closeCategoryPopup() {
this.showCategoryPopup = false;
this.firstClassList = [];
this.secondClassList = [];
this.selectedFirstIndex = -1;
this.selectedSecondId = null;
},
deleteService(item) {
uni.showModal({
@ -386,32 +497,32 @@
});
},
},
};
};
</script>
<style scoped lang="less">
.service-page {
.service-page {
padding-bottom: 120rpx;
}
}
/* 搜索框样式 */
.search-box {
/* 搜索框样式 */
.search-box {
width: 750rpx;
background-color: #ffffff;
border-radius: 8rpx;
padding: 24rpx;
box-sizing: border-box;
position: relative;
}
}
.flexed {
.flexed {
position: sticky;
top: 88rpx;
left: 0;
z-index: 999;
}
}
.search-inp {
.search-inp {
width: 700rpx;
height: 68rpx;
background-color: #eef4fa;
@ -419,23 +530,23 @@
padding: 0 70rpx 0 48rpx;
font-size: 28rpx;
box-sizing: border-box;
}
}
.placeholder {
.placeholder {
color: #999999;
}
}
.search-icon {
.search-icon {
width: 32rpx;
height: 32rpx;
position: absolute;
right: 56rpx;
top: 50%;
transform: translateY(-50%);
}
}
/* 标签栏样式 */
.tab-container {
/* 标签栏样式 */
.tab-container {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
@ -443,23 +554,23 @@
background-color: #ffffff;
padding: 24rpx 24rpx 30rpx;
box-sizing: border-box;
}
}
.tab-item {
.tab-item {
flex: 1;
text-align: center;
font-size: 30rpx;
color: #666666;
padding: 16rpx 0;
position: relative;
}
}
.tab-item.active {
.tab-item.active {
color: #000000;
font-weight: 500;
}
}
.tab-item.active::after {
.tab-item.active::after {
content: "";
position: absolute;
bottom: 0;
@ -469,14 +580,14 @@
height: 6rpx;
background-color: #E8101E;
border-radius: 3rpx;
}
}
/* 服务列表样式 */
.service-list {
/* 服务列表样式 */
.service-list {
margin: 24rpx 24rpx 0;
}
}
.service-item {
.service-item {
height: 248rpx;
position: relative;
background-color: #ffffff;
@ -485,13 +596,13 @@
box-sizing: border-box;
overflow: hidden;
touch-action: pan-y pinch-zoom;
}
}
.service-item:last-child {
.service-item:last-child {
margin-bottom: 0;
}
}
.service-content {
.service-content {
width: 100%;
height: 100%;
padding: 24rpx;
@ -504,9 +615,9 @@
transition: transform 0.3s ease;
position: relative;
z-index: 1;
}
}
.delete-btn {
.delete-btn {
position: absolute;
right: -160rpx;
top: 0;
@ -520,61 +631,61 @@
font-size: 28rpx;
transition: transform 0.3s ease;
z-index: 2;
}
}
.service-item:active .service-content,
.service-item:active .delete-btn {
.service-item:active .service-content,
.service-item:active .delete-btn {
transform: none;
}
}
.service-image {
.service-image {
width: 200rpx;
height: 200rpx;
border-radius: 24rpx;
margin-right: 16rpx;
background-color: rgba(0, 0, 0, 0.05);
}
}
.service-info {
.service-info {
height: 200rpx;
width: 350rpx;
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
align-items: flex-start;
}
}
.service-title {
.service-title {
font-size: 28rpx;
color: #000000;
margin-bottom: 24rpx;
font-weight: 500;
}
}
.service-price {
.service-price {
display: flex;
align-items: baseline;
}
}
.price-symbol {
.price-symbol {
font-size: 24rpx;
color: #E8101E;
font-weight: 500;
}
}
.price-value {
.price-value {
font-size: 28rpx;
color: #E8101E;
font-weight: 500;
margin: 0 4rpx;
}
}
.price-unit {
.price-unit {
font-size: 24rpx;
color: #E8101E;
}
}
.status-tag {
.status-tag {
position: absolute;
top: 0rpx;
right: 0rpx;
@ -585,40 +696,40 @@
text-align: center;
box-sizing: border-box;
font-size: 24rpx;
}
}
/* 已上架 */
.status-tag.state2 {
/* 已上架 */
.status-tag.state2 {
background-color: rgba(232, 16, 30, 0.2);
}
}
.status-tag.state2 .status-text {
.status-tag.state2 .status-text {
color: #E8101E;
}
}
/* 审核中 */
.status-tag.state1 {
/* 审核中 */
.status-tag.state1 {
background-color: rgba(40, 209, 137, 0.2);
}
}
.status-tag.state1 .status-text {
.status-tag.state1 .status-text {
color: #28d189;
}
}
/* 草稿/撤回 */
.status-tag.state0,
.status-tag.state3,
.status-tag.state4 {
/* 草稿/撤回 */
.status-tag.state0,
.status-tag.state3,
.status-tag.state4 {
background-color: rgba(209, 15, 19, 0.2);
}
}
.status-tag.state0 .status-text,
.status-tag.state3 .status-text,
.status-tag.state4 .status-text {
.status-tag.state0 .status-text,
.status-tag.state3 .status-text,
.status-tag.state4 .status-text {
color: #d10f13;
}
}
.btns {
.btns {
position: absolute;
bottom: 26rpx;
right: 24rpx;
@ -626,102 +737,102 @@
flex-flow: row nowrap;
justify-content: flex-end;
align-items: center;
}
}
.view-btn {
.view-btn {
display: flex;
align-items: center;
margin-left: 24rpx;
}
}
.view-icon {
.view-icon {
width: 32rpx;
height: 32rpx;
margin-right: 8rpx;
}
}
.view-text {
.view-text {
font-size: 20rpx;
color: #666666;
}
}
.load-more {
.load-more {
text-align: center;
padding: 20rpx 0;
color: #999999;
font-size: 24rpx;
}
}
.service-item {
.service-item {
cursor: pointer;
user-select: none;
-webkit-user-select: none;
}
}
.service-content,
.delete-btn {
.service-content,
.delete-btn {
will-change: transform;
}
}
.service-nothings {
.service-nothings {
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
padding: 280rpx 0 0;
}
}
.nothings-text {
.nothings-text {
font-size: 32rpx;
font-weight: 500;
color: #999999;
text-align: center;
font-style: italic;
}
}
.add-btn {
.add-btn {
height: 140rpx;
}
}
/* 平台特定样式 */
/* #ifdef H5 */
.service-container {
/* 平台特定样式 */
/* #ifdef H5 */
.service-container {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}
/* #endif */
/* #endif */
/* #ifdef APP-PLUS */
.service-image {
/* #ifdef APP-PLUS */
.service-image {
border: 1rpx solid #eeeeee;
}
}
.flexed {
.flexed {
top: calc(var(--status-bar-height) + 88rpx);
}
}
/* #endif */
/* #endif */
/* #ifdef MP-WEIXIN */
.search-box input {
/* #ifdef MP-WEIXIN */
.search-box input {
/* 小程序搜索框样式调整 */
box-sizing: border-box;
}
}
.flexed {
.flexed {
top: calc(var(--status-bar-height) + 88rpx);
}
}
.service-item {
.service-item {
/* 小程序阴影效果 */
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
}
}
/* #endif */
/* #endif */
.serviceBtn-box {
.serviceBtn-box {
position: fixed;
left: 0px;
bottom: 0px;
@ -746,5 +857,199 @@
}
}
}
}
/* 分类选择弹窗样式 */
.category-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
display: flex;
align-items: flex-end;
justify-content: center;
}
.category-popup {
width: 100%;
height: 70vh;
background-color: #ffffff;
border-radius: 32rpx 32rpx 0 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.category-popup-header {
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx;
position: relative;
border-bottom: 1rpx solid #f0f0f0;
}
.category-popup-title {
font-size: 34rpx;
font-weight: 600;
color: #333333;
}
.category-popup-close {
position: absolute;
right: 32rpx;
top: 50%;
transform: translateY(-50%);
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.category-popup-close-icon {
font-size: 48rpx;
color: #999999;
line-height: 1;
}
.category-popup-body {
flex: 1;
display: flex;
overflow: hidden;
}
.category-left {
width: 220rpx;
background-color: #f5f5f5;
height: 100%;
}
.category-left-item {
padding: 30rpx 24rpx;
text-align: center;
position: relative;
}
.category-left-item.active {
background-color: #ffffff;
}
.category-left-item.active::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 6rpx;
height: 40rpx;
background-color: #E8101E;
border-radius: 0 3rpx 3rpx 0;
}
.category-left-text {
font-size: 28rpx;
color: #333333;
}
.category-left-item.active .category-left-text {
color: #E8101E;
font-weight: 500;
}
.category-right {
flex: 1;
background-color: #ffffff;
height: 100%;
}
.category-right-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 32rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.category-right-text {
font-size: 28rpx;
color: #333333;
flex: 1;
}
.category-dot {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
border: 2rpx solid #dddddd;
margin-left: 16rpx;
position: relative;
}
.category-dot.selected {
border-color: #E8101E;
background-color: #E8101E;
}
.category-dot.selected::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background-color: #ffffff;
}
.category-empty {
display: flex;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.category-empty-text {
font-size: 28rpx;
color: #999999;
}
.category-popup-footer {
display: flex;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f0f0;
background-color: #ffffff;
}
.category-popup-btn {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 44rpx;
font-size: 30rpx;
}
.category-popup-btn-cancel {
background-color: #f5f5f5;
margin-right: 24rpx;
}
.category-popup-btn-cancel text {
color: #666666;
}
.category-popup-btn-confirm {
background-color: #E8101E;
}
.category-popup-btn-confirm text {
color: #ffffff;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -120,14 +120,14 @@ const request = async (options, isUpdate) => {
if (process.env.NODE_ENV === "development") {
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
//baseURL = 'http://60.247.146.5:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "https://app.mrrweb.com.cn";
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
//baseURL = 'http://116.63.163.121:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "https://app.mrrweb.com.cn";
} else {
baseURL = url; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
//baseURL = 'http://60.247.146.5:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
baseURL = "https://app.mrrweb.com.cn";
baseURL = "http://116.63.163.121:93"; // 发布到生产环境时,此处代码会被摇树移除掉。
//baseURL = 'http://116.63.163.121:96'; // 发布到生产环境时,此处代码会被摇树移除掉。
// baseURL = "https://app.mrrweb.com.cn";
console.log("生产环境");
}