意见反馈

This commit is contained in:
tx 2024-12-31 14:53:01 +08:00
parent 0cd54ba8ec
commit 288ce04983
7 changed files with 383 additions and 82 deletions

View File

@ -1,79 +1,9 @@
This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli).
1. key秘钥的回答内容全部为ct 密码为jy888786
2. 在untils下有自定义封装了的蓝牙操作 rpx转化 图片上传 接口封装 权限封装的内容
3. 开发环境node版本为 20.10.0 rn版本为0.74.6 主要使用的ui库为kitten 为国外ui库 可访问 https://akveo.github.io/react-native-ui-kitten/docs/components/card/overview#card
4. 运行:
npm run android
npm run ios
# Getting Started
>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
## Step 1: Start the Metro Server
First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native.
To start Metro, run the following command from the _root_ of your React Native project:
```bash
# using npm
npm start
# OR using Yarn
yarn start
```
## Step 2: Start your Application
Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app:
### For Android
```bash
# using npm
npm run android
# OR using Yarn
yarn android
```
### For iOS
```bash
# using npm
npm run ios
# OR using Yarn
yarn ios
```
If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly.
This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively.
## Step 3: Modifying your App
Now that you have successfully run the app, let's modify it.
1. Open `App.tsx` in your text editor of choice and edit some lines.
2. For **Android**: Press the <kbd>R</kbd> key twice or select **"Reload"** from the **Developer Menu** (<kbd>Ctrl</kbd> + <kbd>M</kbd> (on Window and Linux) or <kbd>Cmd ⌘</kbd> + <kbd>M</kbd> (on macOS)) to see your changes!
For **iOS**: Hit <kbd>Cmd ⌘</kbd> + <kbd>R</kbd> in your iOS Simulator to reload the app and see your changes!
## Congratulations! :tada:
You've successfully run and modified your React Native App. :partying_face:
### Now what?
- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps).
- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started).
# Troubleshooting
If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page.
# Learn More
To learn more about React Native, take a look at the following resources:
- [React Native Website](https://reactnative.dev) - learn more about React Native.
- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment.
- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**.
- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts.
- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native.
打包: ./gradlew assembleRelease

View File

@ -20,6 +20,7 @@ import ExpiredKeysScreen from '../views/device/ExpiredKeysScreen';
import deviceDetailSet from '../views/device/deviceDetailSet';
import HelpCenterPage from '../views/user/HelpCenterPage';
import XmlRenderPage from '../views/user/XmlRenderPage';
import Feedback from '../views/user/feedback';
const Stack = createStackNavigator<HomeStackParamList>();
const createScreenOptions = (title: string): StackNavigationOptions => {
@ -62,6 +63,13 @@ export default function HomeStackNavigator({ navigation, route }: Props) {
headerShown: false,
}}
/>
<Stack.Screen
name="Feedback"
component={Feedback}
options={{
headerShown: false,
}}
/>
<Stack.Screen
name="ShareDetailScreen"
component={ShareDetailScreen}

View File

@ -33,6 +33,7 @@ export type HomeStackParamList = {
deviceDetailSet: undefined;
HelpCenterPage: undefined;
XmlRenderPage: undefined;
Feedback: undefined;
};
// 导航属性类型

View File

@ -143,5 +143,10 @@ export const apiService = {
getArticleById: (id: string) => api.get('/app/article/' + id),
// 获取反馈类型
getFeedbackType: () => api.get('/appVerify/getDictData?dictType=as_feedback_type'),
// 提交反馈
postFeedback: (data: any) => api.post('/appVerify/feedback', data),
// updateKeyExpiration: (keyId: string, expirationTime: string) => api.put('/appVerify/updateKeyExpiration', { keyId, expirationTime }),
};

View File

@ -121,7 +121,7 @@ const ProfileScreen = () => {
<Text style={styles.itemText}>意见和反馈</Text>
</TouchableOpacity>
{userInfo.isAuthentication === false && (
{/* {userInfo.isAuthentication === false && (
<TouchableOpacity
style={styles.item}
onPress={() => navigation.navigate('IdVerification')}
@ -132,7 +132,7 @@ const ProfileScreen = () => {
/>
<Text style={styles.itemText}>实名认证</Text>
</TouchableOpacity>
)}
)} */}
{/* {userInfo.userType === '02' && (
<TouchableOpacity

View File

@ -109,13 +109,15 @@ export default function DeviceList() {
</View>
<Text style={styles.mileage}>{item.remainingMileage}KM</Text>
</View>
<Text style={styles.model}>{item.model}</Text>
<Text style={styles.model}>
{item.remark ? `备注:${item.remark}` : `车型:${item.model}`}
</Text>
<Text style={styles.carNum}>{item.vehicleNum}</Text>
<Text style={styles.status}>{item.type == 1 ? '车主' : '临时租赁'}</Text>
</View>
<View style={styles.rightContent}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uB1F5aibooguILH8uB4F' }}
source={{ uri:item.picture ? item.picture : 'https://lxnapi.ccttiot.com/bike/img/static/uB1F5aibooguILH8uB4F' }}
style={styles.carImage}
/>
<View style={styles.checkboxWrapper}>

355
src/views/user/feedback.tsx Normal file
View File

@ -0,0 +1,355 @@
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, Image, StyleSheet, ScrollView, ImageBackground } from 'react-native';
import { TopNavigation, TopNavigationAction, Icon } from '@ui-kitten/components';
import { useNavigation } from '@react-navigation/native';
import { apiService } from '../../utils/api';
import { rpx } from '../../utils/rpx';
import { uploadImageService } from '../../utils/uploadImg';
import Toast from 'react-native-toast-message';
const BackIcon = (props: any) => (
<Icon {...props} name='arrow-back' />
);
const Feedback = () => {
const [currentCount, setCurrentCount] = useState(0);
const [textValue, setTextValue] = useState('');
const [imglist, setImglist] = useState<string[]>([]);
const [list, setList] = useState<any[]>([]);
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
const [contactInfo, setContactInfo] = useState('');
const [type, setType] = useState<number | null>(null);
const [isFocused, setIsFocused] = useState(false);
const navigation = useNavigation();
useEffect(() => {
getlist();
}, []);
const toggleCheckbox = (index: number, item: any) => {
setSelectedIndex(index);
setType(item.dictSort);
};
const deleteImage = (index: number) => {
Toast.show({
type: 'info',
text1: '提示',
text2: '确定要删除这张图片吗?',
onPress: () => setImglist(prev => prev.filter((_, i) => i !== index)),
});
};
const sub = () => {
if (selectedIndex === null) {
Toast.show({ type: 'error', text1: '请选择反馈类型' });
return;
}
if (contactInfo === '') {
Toast.show({ type: 'error', text1: '请输入联系方式' });
return;
}
if (!textValue.trim()) {
Toast.show({ type: 'error', text1: '请填写问题描述' });
return;
}
const data = {
issueDescription: textValue,
uploadedImage: imglist.join(','),
type,
contactInfo,
};
apiService.postFeedback(data).then((res) => {
if (res.code == 200) {
Toast.show({ type: 'success', text1: '提交成功' });
navigation.goBack();
} else {
Toast.show({ type: 'error', text1: res.msg });
}
});
};
const getlist = async () => {
const res = await apiService.getFeedbackType();
if (res.code === 200) {
setList(res.data);
} else {
Toast.show({ type: 'error', text1: '获取反馈类型失败' });
}
};
const updateWordCount = (text: string) => {
const count = text.trim().replace(/\s+/g, '').length;
if (count > 500) {
setTextValue(text.slice(0, 500));
setCurrentCount(500);
Toast.show({ type: 'info', text1: '字数已达到500字上限' });
} else {
setTextValue(text);
setCurrentCount(count);
}
};
const handleImageUpload = async () => {
const urls = await uploadImageService.uploadImage({ multiple: true });
if (urls.length > 0) {
setImglist(prev => [...prev, ...urls]);
}
};
const navigateBack = () => {
navigation.goBack();
};
const BackAction = () => (
<TopNavigationAction icon={BackIcon} onPress={navigateBack} />
);
return (
<ImageBackground
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/uYRs7Cv2Pbp95w3KjGO3' }}
style={styles.background}
>
<TopNavigation
title='意见与反馈'
alignment='center'
accessoryLeft={BackAction}
style={styles.topNavigation}
/>
<ScrollView style={styles.page}>
<View style={styles.cardbox}>
<Text style={styles.tip}><Text style={styles.ipnt}>*</Text></Text>
<View style={styles.checkbox}>
{list.map((item, index) => (
<TouchableOpacity
key={index}
style={[styles.check_li, selectedIndex === index && styles.act1]}
onPress={() => toggleCheckbox(index, item)}
>
<Text style={styles.check_li_text}>{item.dictLabel}</Text>
{selectedIndex === index && (
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/bike/img/static/u2jYgqIRlFcHjt20uA90' }}
style={styles.checkIcon}
/>
)}
</TouchableOpacity>
))}
</View>
</View>
<View style={styles.cardbox}>
<Text style={styles.tip}><Text style={styles.ipnt}>*</Text></Text>
<View style={styles.inputContainer}>
{!textValue && !isFocused && <Text style={styles.placeholder}></Text>}
<TextInput
style={styles.customTextarea}
value={textValue}
onChangeText={updateWordCount}
multiline
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
<Text style={styles.wordCount}>{currentCount}/500</Text>
</View>
</View>
<View style={styles.cardbox}>
<Text style={styles.tip}></Text>
<View style={styles.icon}>
{imglist.map((item, index) => (
<View key={index} style={styles.imgbox}>
<Image source={{ uri: item }} style={styles.image} />
<TouchableOpacity style={styles.deleteBtn} onPress={() => deleteImage(index)}>
<Image
source={{ uri: 'https://lxnapi.ccttiot.com/smartmeter/img/static/ui63BrTf8rlk4VAGx8Tf' }}
style={styles.deleteIcon}
/>
</TouchableOpacity>
</View>
))}
{imglist.length < 9 && (
<TouchableOpacity style={[styles.imgbox, styles.addBtn]} onPress={handleImageUpload}>
<Image
source={{ uri: 'https://api.ccttiot.com/smartmeter/img/static/uY8CPw9YE6JxPzcHUaqf' }}
style={styles.image}
/>
</TouchableOpacity>
)}
</View>
</View>
<View style={styles.cardbox}>
<Text style={styles.tip}><Text style={styles.ipnt}>*</Text></Text>
<View style={styles.inputBox}>
<TextInput
style={styles.input}
placeholder="请留下手机号/邮箱/微信号,以便我们回复您"
value={contactInfo}
onChangeText={setContactInfo}
/>
</View>
</View>
<TouchableOpacity style={styles.btn} onPress={sub}>
<Text style={styles.btnText}></Text>
</TouchableOpacity>
</ScrollView>
<Toast />
</ImageBackground>
);
};
const styles = StyleSheet.create({
background: {
flex: 1,
},
topNavigation: {
backgroundColor: 'transparent',
},
page: {
flex: 1,
paddingBottom: rpx(80),
},
cardbox: {
margin: rpx(30),
padding: rpx(20),
backgroundColor: '#FFFFFF',
borderRadius: rpx(20),
shadowColor: '#000',
shadowOffset: { width: 0, height: rpx(10) },
shadowOpacity: 0.08,
shadowRadius: rpx(10),
elevation: 2,
},
tip: {
fontWeight: '700',
fontSize: rpx(32),
color: '#3D3D3D',
marginBottom: rpx(10),
},
ipnt: {
color: '#FF4444',
fontSize: rpx(48),
},
checkbox: {
flexDirection: 'row',
flexWrap: 'wrap',
},
check_li: {
marginRight: rpx(10),
marginTop: rpx(18),
padding: rpx(18),
borderRadius: rpx(25),
borderWidth: rpx(2),
borderColor: '#C4C4C4',
flexDirection: 'row',
alignItems: 'center',
},
act1: {
borderColor: '#4297F3',
},
check_li_text: {
fontSize: rpx(28),
},
checkIcon: {
position: 'absolute',
right: rpx(0),
bottom: rpx(0),
width: rpx(40),
height: rpx(20),
marginLeft: rpx(5),
borderBottomRightRadius: rpx(20),
},
inputContainer: {
position: 'relative',
height: rpx(248),
borderWidth: rpx(2),
borderColor: '#C7C7C7',
borderRadius: rpx(20),
padding: rpx(10),
marginTop: rpx(40),
},
placeholder: {
position: 'absolute',
top: rpx(18),
left: rpx(38),
color: '#999',
},
customTextarea: {
flex: 1,
textAlignVertical: 'top',
paddingTop: rpx(18),
paddingLeft: rpx(38),
},
wordCount: {
position: 'absolute',
right: rpx(10),
bottom: rpx(10),
fontSize: rpx(12),
color: '#999',
},
icon: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
marginTop: rpx(40),
},
imgbox: {
position: 'relative',
width: '30%',
marginBottom: rpx(20),
marginRight: rpx(10),
},
image: {
width: rpx(142),
height: rpx(142),
borderRadius: rpx(10),
},
deleteBtn: {
position: 'absolute',
top: rpx(-16),
right: rpx(-16),
width: rpx(32),
height: rpx(32),
backgroundColor: '#DCEDFF',
borderRadius: rpx(16),
justifyContent: 'center',
alignItems: 'center',
},
deleteIcon: {
width: rpx(20),
height: rpx(20),
},
addBtn: {
justifyContent: 'center',
alignItems: 'center',
},
inputBox: {
marginTop: rpx(40),
borderWidth: rpx(2),
borderColor: '#C7C7C7',
borderRadius: rpx(20),
padding: rpx(10),
},
input: {
height: rpx(80),
paddingLeft: rpx(20),
},
btn: {
margin: rpx(34),
padding: rpx(15),
backgroundColor: '#4297F3',
borderRadius: rpx(54),
alignItems: 'center',
},
btnText: {
color: '#FFFFFF',
fontSize: rpx(40),
fontWeight: '500',
},
});
export default Feedback;