意见反馈
This commit is contained in:
parent
0cd54ba8ec
commit
288ce04983
86
README.md
86
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -33,6 +33,7 @@ export type HomeStackParamList = {
|
|||
deviceDetailSet: undefined;
|
||||
HelpCenterPage: undefined;
|
||||
XmlRenderPage: undefined;
|
||||
Feedback: undefined;
|
||||
};
|
||||
|
||||
// 导航属性类型
|
||||
|
|
|
@ -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 }),
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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
355
src/views/user/feedback.tsx
Normal 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;
|
Loading…
Reference in New Issue
Block a user