123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- import 'package:flutter/material.dart';
- import 'dart:io';
- import 'package:image_picker/image_picker.dart';
- class EscortPublish extends StatefulWidget {
- @override
- State<EscortPublish> createState() => _EscortPublishState();
- }
- class _EscortPublishState extends State<EscortPublish> {
- final _nameController = TextEditingController();
- final _phoneController = TextEditingController();
- final _addressController = TextEditingController();
- final List<String> levels = ['平价', '中端', '高端', '顶级'];
- final List<String> services = ['工作室', '上门', '空降', '伴游'];
- final List<String> taboos = ['DU品', '吃YAO', 'WU套', '醉9'];
- final List<String> tags = [
- '学生',
- '良家',
- 'OL',
- '网红',
- '模特',
- '明星',
- '新人',
- '兼职',
- '短期',
- '原装',
- 'S身材',
- '高颜值',
- '三点粉',
- '微胖',
- '水嫩',
- '紧致',
- '清纯',
- '轻熟',
- '气质',
- '纯欲'
- ];
- final List<String> packages = [
- '双飞',
- '3P',
- '口爆',
- '胸推',
- '足交',
- '毒龙',
- '做爱',
- '69',
- '无套内射',
- '舌吻',
- '水床',
- '制服',
- '冰火',
- '环游',
- '调教',
- '喷水',
- '洗澡',
- '剧情',
- 'SM',
- '三通'
- ];
- String? selectedLevel;
- String? selectedCity;
- String? selectedYear;
- String? selectedWeight;
- String? selectedHeight;
- String? selectedCup;
- List<String> selectedServices = [];
- List<String> selectedTaboos = [];
- List<String> selectedTags = [];
- List<String> selectedPackages = [];
- List<XFile> imageFiles = [];
- XFile? videoFile;
- bool hideFromPublic = false;
- final TextEditingController _descController = TextEditingController();
- final ImagePicker _picker = ImagePicker();
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: const Text.rich(
- TextSpan(
- text: '发布外围',
- children: [
- TextSpan(
- text: '(请保证妹子信息准确)',
- style: TextStyle(fontSize: 14, color: Colors.grey),
- )
- ],
- ),
- ),
- leading: const BackButton(),
- ),
- body: SingleChildScrollView(
- padding: const EdgeInsets.all(16),
- child: Column(
- children: [
- buildTextField("外围花名", "(必填)", "请输入外围花名,不超过10个字", _nameController),
- buildDropdownField("所在城市", "(必选)", selectedCity, ['北京', '上海', '广州'],
- (val) {
- setState(() => selectedCity = val);
- }),
- buildTextField("手机号码", "(必填,不会自动发送给用户)", "请输入妹子联系方式,不超过50个字",
- _phoneController),
- buildTextField("详细地址", "(必填,不会自动发送给用户)", "请输入妹子工作室地址,不超过50个字",
- _addressController),
- buildDropdownField("出生年份", "(必选)", selectedYear,
- List.generate(25, (i) => '${2000 - i}'), (val) {
- setState(() => selectedYear = val);
- }),
- buildDropdownField(
- "体重", "(必选)", selectedWeight, ['40kg', '45kg', '50kg', '55kg'],
- (val) {
- setState(() => selectedWeight = val);
- }),
- buildDropdownField(
- "身高", "(必选)", selectedHeight, ['150cm', '160cm', '170cm'],
- (val) {
- setState(() => selectedHeight = val);
- }),
- buildDropdownField(
- "罩杯", "(必选)", selectedCup, ['A', 'B', 'C', 'D', 'E'], (val) {
- setState(() => selectedCup = val);
- }),
- const SizedBox(height: 20),
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '请选择妹子档次'),
- TextSpan(text: '(单选)', style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- const SizedBox(height: 10),
- Wrap(
- spacing: 12,
- children: levels.map((level) {
- final selected = level == selectedLevel;
- return ChoiceChip(
- label: Text(level),
- selected: selected,
- onSelected: (_) {
- setState(() => selectedLevel = level);
- },
- selectedColor: Colors.blue,
- labelStyle:
- TextStyle(color: selected ? Colors.white : Colors.black),
- );
- }).toList(),
- ),
- const SizedBox(height: 20),
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '服务套餐'),
- TextSpan(
- text: '(最少选填1项)', style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- const SizedBox(height: 10),
- buildSelectableChips(
- options: services,
- selectedList: selectedServices,
- maxSelect: services.length,
- isSingle: false,
- onTap: (_) {},
- ),
- const SizedBox(height: 12),
- buildPackageInputFields(),
- const SizedBox(height: 20),
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '禁忌'),
- TextSpan(text: '(多选)', style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- const SizedBox(height: 10),
- buildSelectableChips(
- options: taboos,
- selectedList: selectedTaboos,
- maxSelect: taboos.length,
- isSingle: false,
- onTap: (_) {},
- ),
- const SizedBox(height: 20),
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '外圈标签'),
- TextSpan(
- text: '(必选,最多3个)', style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- const SizedBox(height: 10),
- buildSelectableChips(
- options: tags,
- selectedList: selectedTags,
- maxSelect: 3,
- isSingle: false,
- onTap: (_) {},
- ),
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '服务项目'),
- TextSpan(
- text: '(必选,最多20个)',
- style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- const SizedBox(height: 10),
- buildSelectableChips(
- options: packages,
- selectedList: selectedPackages,
- maxSelect: 20,
- isSingle: false,
- onTap: (_) {},
- ),
- const SizedBox(height: 20),
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '外圈介绍'),
- TextSpan(text: '(选填)', style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- const SizedBox(height: 10),
- TextField(
- controller: _descController,
- maxLength: 200,
- maxLines: 4,
- decoration: const InputDecoration(
- hintText: '请输入外圈介绍,不超过200字',
- border: OutlineInputBorder(),
- ),
- ),
- const SizedBox(height: 20),
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '上传图片'),
- TextSpan(
- text: '(最少1张,最多9张)',
- style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- const SizedBox(height: 10),
- buildImageUploader(),
- const SizedBox(height: 20),
- Row(
- children: [
- Align(
- alignment: Alignment.centerLeft,
- child: RichText(
- text: const TextSpan(
- style: TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: '官方认证视频'),
- TextSpan(
- text: '(选填,不超过100M)',
- style: TextStyle(color: Colors.red)),
- ],
- ),
- ),
- ),
- Spacer(),
- Row(
- children: [
- Radio<bool>(
- value: true,
- groupValue: hideFromPublic,
- onChanged: (_) => setState(() => hideFromPublic = true),
- ),
- const Text("不对外展示", style: TextStyle(fontSize: 12)),
- ],
- ),
- ],
- ),
- const SizedBox(height: 8),
- GestureDetector(
- onTap: pickVideo,
- child: Container(
- height: 160,
- color: Colors.grey[100],
- child: Center(
- child: videoFile == null
- ? const Icon(Icons.videocam, size: 40, color: Colors.grey)
- : const Icon(Icons.check_circle,
- color: Colors.green, size: 40),
- ),
- ),
- ),
- const SizedBox(height: 8),
- const Text("① 视频需包含本人露脸、当前日期、所在城市/名媛会",
- style: TextStyle(color: Colors.grey)),
- const Text("例如:3月3日,名媛会露脸在苏州等你",
- style: TextStyle(color: Colors.grey)),
- const Text("② 审核通过率高,通过后可获得官方认证标识",
- style: TextStyle(color: Colors.grey)),
- const Text("③ 选择对外展示可额外获得官方流量扶持+佣金优惠",
- style: TextStyle(color: Colors.grey)),
- const SizedBox(height: 30),
- SizedBox(
- width: double.infinity,
- child: ElevatedButton(
- onPressed: () {},
- style: ElevatedButton.styleFrom(
- padding: const EdgeInsets.symmetric(vertical: 16),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(6)),
- backgroundColor: Colors.blue,
- ),
- child: const Text("提交", style: TextStyle(fontSize: 16)),
- ),
- ),
- ],
- ),
- ),
- );
- }
- Widget buildTextField(String label, String hint, String placeholder,
- TextEditingController controller) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- buildLabel(label, hint),
- const SizedBox(height: 6),
- TextField(
- controller: controller,
- decoration: InputDecoration(
- hintText: placeholder,
- border: OutlineInputBorder(),
- contentPadding:
- const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
- ),
- ),
- ],
- ),
- );
- }
- Widget buildDropdownField(String label, String hint, String? value,
- List<String> options, ValueChanged<String?> onChanged) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- buildLabel(label, hint),
- const SizedBox(height: 6),
- DropdownButtonFormField<String>(
- value: value,
- items: options
- .map((e) => DropdownMenuItem(value: e, child: Text(e)))
- .toList(),
- onChanged: onChanged,
- decoration: InputDecoration(
- border: OutlineInputBorder(),
- contentPadding:
- const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
- ),
- ),
- ],
- ),
- );
- }
- Widget buildLabel(String title, String hint) {
- return RichText(
- text: TextSpan(
- text: title,
- style: const TextStyle(fontSize: 16, color: Colors.black),
- children: [
- TextSpan(text: ' $hint', style: const TextStyle(color: Colors.red)),
- ],
- ),
- );
- }
- Widget buildSelectableChips({
- required List<String> options,
- required List<String> selectedList,
- required int maxSelect,
- required bool isSingle,
- required Function(String) onTap,
- }) {
- return Wrap(
- spacing: 8,
- runSpacing: 8,
- children: options.map((option) {
- final bool selected = selectedList.contains(option);
- return ChoiceChip(
- label: Text(option),
- selected: selected,
- onSelected: (_) {
- setState(() {
- if (isSingle) {
- selectedList
- ..clear()
- ..add(option);
- } else {
- if (selected) {
- selectedList.remove(option);
- } else if (selectedList.length < maxSelect) {
- selectedList.add(option);
- }
- }
- onTap(option);
- });
- },
- );
- }).toList(),
- );
- }
- Widget buildPackageInputFields() {
- return Column(
- children: List.generate(4, (i) {
- return Row(
- children: [
- Expanded(
- child: TextField(
- decoration: InputDecoration(
- hintText: '请输入服务套餐${i + 1}${i == 0 ? ',必填' : ''}',
- border: OutlineInputBorder(),
- contentPadding:
- const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
- ),
- ),
- ),
- const SizedBox(width: 8),
- Expanded(
- child: TextField(
- decoration: InputDecoration(
- hintText: '请输入套餐${i + 1}价格${i == 0 ? ',必填' : ''}',
- border: OutlineInputBorder(),
- contentPadding:
- const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
- ),
- keyboardType: TextInputType.number,
- ),
- ),
- ],
- );
- }),
- );
- }
- Widget buildImageUploader() {
- return Wrap(
- spacing: 8,
- runSpacing: 8,
- children: List.generate(9, (index) {
- if (index < imageFiles.length) {
- return Stack(
- children: [
- Image.file(
- File(imageFiles[index].path),
- width: 100,
- height: 100,
- fit: BoxFit.cover,
- ),
- Positioned(
- top: 0,
- right: 0,
- child: GestureDetector(
- onTap: () {
- setState(() {
- imageFiles.removeAt(index);
- });
- },
- child: const Icon(Icons.cancel, color: Colors.red, size: 20),
- ),
- ),
- ],
- );
- } else if (index == imageFiles.length) {
- return GestureDetector(
- onTap: pickImages,
- child: Container(
- width: 100,
- height: 100,
- color: Colors.grey[200],
- child: const Icon(Icons.add),
- ),
- );
- } else {
- return Container(
- width: 100,
- height: 100,
- color: Colors.grey[100],
- );
- }
- }),
- );
- }
- Future<void> pickVideo() async {
- final XFile? picked = await _picker.pickVideo(
- source: ImageSource.gallery,
- maxDuration: const Duration(minutes: 5),
- );
- if (picked != null) {
- setState(() {
- videoFile = picked;
- });
- }
- }
- Future<void> pickImages() async {
- final List<XFile>? images = await _picker.pickMultiImage();
- if (images != null && images.isNotEmpty) {
- setState(() {
- final remainingSlots = 9 - imageFiles.length;
- imageFiles.addAll(images.take(remainingSlots));
- });
- }
- }
- }
|