android软件开发之仿淘宝选择规格的实现
在一些app开发项目中选择商品规格这个功能最容易遇到问题,想要实现需要的全部功能,但一直没有成功,所以就去找了个Demo,学习界面UI采用recyclerview,item里面渲染ViewGroup,根据数据源的数量,往ViewGroup里面添加Textview。这样就可以解决它的每个属性按钮宽高自适应。下面来详细分享一下源码:
/**
- 测量子view大小 根据子控件设置宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获得它的父容器为它设置的测量模式和大小
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 如果是warp_content情况下,记录宽和高
int width = 0;
int height = 0;
/**
- 记录每一行的宽度,width不断取最大宽度
*/
int lineWidth = 0;
/**
- 每一行的高度,累加至height
*/
int lineHeight = 0;
int cCount = getChildCount();
// 遍历每个子元素
for (int i = 0; i < cCount; i++)
{
View child = getChildAt(i);
// 测量每一个child的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到child的布局管理器
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
// 当前子空间实际占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin
- lp.rightMargin;
// 当前子空间实际占据的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin
- lp.bottomMargin;
/**
- 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行
*/
if (lineWidth + childWidth > sizeWidth)
{
width = Math.max(lineWidth, childWidth);// 取最大的
lineWidth = childWidth; // 重新开启新行,开始记录
// 叠加当前高度,
height += lineHeight;
// 开启记录下一行的高度
lineHeight = childHeight;
} else
// 否则累加值lineWidth,lineHeight取最大高度
{
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
// 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
if (i == cCount - 1)
{
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
- setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ?sizeWidth
width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
mAllViews.clear();
mLineHeight.clear();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
// 存储每一行所有的childView
ListlineViews = new ArrayList<>();
int cCount = getChildCount();
// 遍历所有的孩子
for (int i = 0; i < cCount; i++)
{
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// 如果已经需要换行
if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width)
{
// 记录这一行所有的View以及最大高度
mLineHeight.add(lineHeight);
// 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
mAllViews.add(lineViews);
lineWidth = 0;// 重置行宽
lineViews = new ArrayList<>();
}
/**
- 如果不需要换行,则累加
*/
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
- lp.bottomMargin);
lineViews.add(child);
}
// 记录最后一行
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
int left = 0;
int top = 0;
// 得到总行数
int lineNums = mAllViews.size();
for (int i = 0; i < lineNums; i++)
{
// 每一行的所有的views
lineViews = mAllViews.get(i);
// 当前行的最大高度
lineHeight = mLineHeight.get(i);
// 遍历当前行所有的View
for (int j = 0; j < lineViews.size(); j++)
{
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE)
{
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
//计算childView的Marginleft,top,right,bottom
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc =lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.rightMargin
- lp.leftMargin;
}
left = 0;
top += lineHeight;
}
}
接下来是SKU的算法,把选项状态(三种:不能选择,可以选择,已选中)依次对属性按钮做出修改,这里虽然做了一些不必要的循环判断,但胜在功能的实现adapter代码(重点initOptions、canClickOptions和getSelected三个方法)
public class GoodsAttrsAdapter extends BaseRecyclerAdapter {
private SKUInterface myInterface;
private SimpleArrayMap saveClick;
private List stockGoodsList;//商品数据集合
private String[] selectedValue; //选中的属性
private TextView[][] childrenViews; //二维 装所有属性
private final int SELECTED = 0x100;
private final int CANCEL = 0x101;
public GoodsAttrsAdapter(Context ctx, List list, List stockGoodsList) {
super(ctx, list);
this.stockGoodsList = stockGoodsList;
saveClick = new SimpleArrayMap<>();
childrenViews = new TextView[list.size()][0];
selectedValue = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
selectedValue[i] = "";
}
}
public void setSKUInterface(SKUInterface myInterface) {
this.myInterface = myInterface;
}
@Override
public int getItemLayoutId(int viewType) {
return R.layout.item_skuattrs;
}
@Override
public void bindData(RecyclerViewHolder holder, int position, GoodsAttrsBean.AttributesBean item) {
TextView tv_ItemName = holder.getTextView(R.id.tv_ItemName);
SKUViewGroup vg_skuItem = (SKUViewGroup) holder.getView(R.id.vg_skuItem);
tv_ItemName.setText(item.getTabName());
Listchildrens = item.getAttributesItem();
int childrenSize = childrens.size();
TextView[] textViews = new TextView[childrenSize];
for (int i = 0; i < childrenSize; i++) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(5, 5, 5, 0);
TextView textView = new TextView(mContext);
textView.setGravity(Gravity.CENTER);
textView.setPadding(15, 5, 15, 5);
textView.setLayoutParams(params);
textView.setBackgroundColor(ContextCompat.getColor(mContext, R.color.saddlebrown));
textView.setText(childrens.get(i));
textView.setTextColor(ContextCompat.getColor(mContext, R.color.white));
textViews[i] = textView;
vg_skuItem.addView(textViews[i]);
}
childrenViews[position] = textViews;
initOptions();
canClickOptions();
getSelected();
}
private int focusPositionG, focusPositionC;
private class MyOnClickListener implements View.OnClickListener {
//点击操作 选中SELECTED 取消CANCEL
private int operation;
private int positionG;
private int positionC;
public MyOnClickListener(int operation, int positionG, int positionC) {
this.operation = operation;
this.positionG = positionG;
this.positionC = positionC;
}
@Override
public void onClick(View v) {
focusPositionG = positionG;
focusPositionC = positionC;
String value = childrenViews[positionG][positionC].getText().toString();
switch (operation) {
case SELECTED:
saveClick.put(positionG, positionC + "");
selectedValue[positionG] = value;
myInterface.selectedAttribute(selectedValue);
break;
case CANCEL:
saveClick.put(positionG, "");
for (int l = 0; l < selectedValue.length; l++) {
if (selectedValue[l].equals(value)) {
selectedValue[l] = "";
break;
}
}
myInterface.uncheckAttribute(selectedValue);
break;
}
initOptions();
canClickOptions();
getSelected();
}
}
class MyOnFocusChangeListener implements View.OnFocusChangeListener {
private int positionG;
private int positionC;
public MyOnFocusChangeListener(int positionG, int positionC) {
this.positionG = positionG;
this.positionC = positionC;
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
String clickpositionC = saveClick.get(positionG);
if (hasFocus) {
v.setBackgroundColor(ContextCompat.getColor(mContext, R.color.pink));
if (TextUtils.isEmpty(clickpositionC)) {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.dodgerblue));
} else if (clickpositionC.equals(positionC + "")) {
} else {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.dodgerblue));
}
} else {
v.setBackgroundColor(ContextCompat.getColor(mContext, R.color.saddlebrown));
if (TextUtils.isEmpty(clickpositionC)) {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.white));
} else if (clickpositionC.equals(positionC + "")) {
} else {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.white));
}
}
}
}
/**
- 初始化选项(不可点击,焦点消失)
*/
private void initOptions() {
for (int y = 0; y < childrenViews.length; y++) {
for (int z = 0; z < childrenViews[y].length; z++) {//循环所有属性
TextView textView = childrenViews[y][z];
textView.setEnabled(false);
textView.setFocusable(false);
textView.setTextColor(ContextCompat.getColor(mContext, R.color.gray));//变灰
}
}
}
/**
- 找到符合条件的选项变为可选
*/
private void canClickOptions() {
for (int i = 0; i < childrenViews.length; i++) {
for (int j = 0; j < stockGoodsList.size(); j++) {
boolean filter = false;
List goodsInfo = stockGoodsList.get(j).getGoodsInfo();
for (int k = 0; k < selectedValue.length; k++) {
if (i == k || TextUtils.isEmpty(selectedValue[k])) {
continue;
}
if (!selectedValue[k].equals(goodsInfo
.get(k).getTabValue())) {
filter = true;
break;
}
}
if (!filter) {
for (int n = 0; n < childrenViews[i].length; n++) {
TextView textView = childrenViews[i][n];//拿到所有属性TextView
String name = textView.getText().toString();
//拿到属性名称
if (goodsInfo.get(i).getTabValue().equals(name)) {
textView.setEnabled(true);//符合就变成可点击
textView.setFocusable(true); //设置可以获取焦点
//不要让焦点乱跑
if (focusPositionG == i && focusPositionC == n) {
textView.setTextColor(ContextCompat.getColor(mContext, R.color.dodgerblue));
textView.requestFocus();
} else {
textView.setTextColor(ContextCompat.getColor(mContext, R.color.white));
}
textView.setOnClickListener(new MyOnClickListener(SELECTED, i, n) {
});
textView.setOnFocusChangeListener(new MyOnFocusChangeListener(i, n) {
});
}
}
}
}
}
}
/**
- 找到已经选中的选项,让其变红
*/
private void getSelected() {
for (int i = 0; i < childrenViews.length; i++) {
for (int j = 0; j < childrenViews[i].length; j++) {//拿到每行属性Item
TextView textView = childrenViews[i][j];//拿到所有属性TextView
String value = textView.getText().toString();
for (int m = 0; m < selectedValue.length; m++) {
if (selectedValue[m].equals(value)) {
textView.setTextColor(ContextCompat.getColor(mContext, R.color.red));
textView.setOnClickListener(new MyOnClickListener(CANCEL, i, j) {
});
}
}
}
}
}
}
好了,到这里就算是结束了,如果还是存在有疑问的地方,大家可以留言咨询。
本文由专业的郑州app开发公司燚轩科技整理发布,原创不易,如需转载请注明出处!
在一些app开发项目中选择商品规格这个功能最容易遇到问题,想要实现需要的全部功能,但一直没有成功,所以就去找了个Demo,学习界面UI采用recyclerview,item里面渲染ViewGroup,根据数据源的数量,往ViewGroup里面添加Textview。这样就可以解决它的每个属性按钮宽高自适应。下面来详细分享一下源码:
/**
- 测量子view大小 根据子控件设置宽和高
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获得它的父容器为它设置的测量模式和大小
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
// 如果是warp_content情况下,记录宽和高
int width = 0;
int height = 0;
/**
- 记录每一行的宽度,width不断取最大宽度
*/
int lineWidth = 0;
/**
- 每一行的高度,累加至height
*/
int lineHeight = 0;
int cCount = getChildCount();
// 遍历每个子元素
for (int i = 0; i < cCount; i++)
{
View child = getChildAt(i);
// 测量每一个child的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到child的布局管理器
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
// 当前子空间实际占据的宽度
int childWidth = child.getMeasuredWidth() + lp.leftMargin
- lp.rightMargin;
// 当前子空间实际占据的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin
- lp.bottomMargin;
/**
- 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行
*/
if (lineWidth + childWidth > sizeWidth)
{
width = Math.max(lineWidth, childWidth);// 取最大的
lineWidth = childWidth; // 重新开启新行,开始记录
// 叠加当前高度,
height += lineHeight;
// 开启记录下一行的高度
lineHeight = childHeight;
} else
// 否则累加值lineWidth,lineHeight取最大高度
{
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, childHeight);
}
// 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
if (i == cCount - 1)
{
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
- setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ?sizeWidth
width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
mAllViews.clear();
mLineHeight.clear();
int width = getWidth();
int lineWidth = 0;
int lineHeight = 0;
// 存储每一行所有的childView
ListlineViews = new ArrayList<>();
int cCount = getChildCount();
// 遍历所有的孩子
for (int i = 0; i < cCount; i++)
{
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// 如果已经需要换行
if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width)
{
// 记录这一行所有的View以及最大高度
mLineHeight.add(lineHeight);
// 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
mAllViews.add(lineViews);
lineWidth = 0;// 重置行宽
lineViews = new ArrayList<>();
}
/**
- 如果不需要换行,则累加
*/
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
- lp.bottomMargin);
lineViews.add(child);
}
// 记录最后一行
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
int left = 0;
int top = 0;
// 得到总行数
int lineNums = mAllViews.size();
for (int i = 0; i < lineNums; i++)
{
// 每一行的所有的views
lineViews = mAllViews.get(i);
// 当前行的最大高度
lineHeight = mLineHeight.get(i);
// 遍历当前行所有的View
for (int j = 0; j < lineViews.size(); j++)
{
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE)
{
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
//计算childView的Marginleft,top,right,bottom
int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc =lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();
child.layout(lc, tc, rc, bc);
left += child.getMeasuredWidth() + lp.rightMargin
- lp.leftMargin;
}
left = 0;
top += lineHeight;
}
}
接下来是SKU的算法,把选项状态(三种:不能选择,可以选择,已选中)依次对属性按钮做出修改,这里虽然做了一些不必要的循环判断,但胜在功能的实现adapter代码(重点initOptions、canClickOptions和getSelected三个方法)
public class GoodsAttrsAdapter extends BaseRecyclerAdapter {
private SKUInterface myInterface;
private SimpleArrayMap saveClick;
private List stockGoodsList;//商品数据集合
private String[] selectedValue; //选中的属性
private TextView[][] childrenViews; //二维 装所有属性
private final int SELECTED = 0x100;
private final int CANCEL = 0x101;
public GoodsAttrsAdapter(Context ctx, List list, List stockGoodsList) {
super(ctx, list);
this.stockGoodsList = stockGoodsList;
saveClick = new SimpleArrayMap<>();
childrenViews = new TextView[list.size()][0];
selectedValue = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
selectedValue[i] = "";
}
}
public void setSKUInterface(SKUInterface myInterface) {
this.myInterface = myInterface;
}
@Override
public int getItemLayoutId(int viewType) {
return R.layout.item_skuattrs;
}
@Override
public void bindData(RecyclerViewHolder holder, int position, GoodsAttrsBean.AttributesBean item) {
TextView tv_ItemName = holder.getTextView(R.id.tv_ItemName);
SKUViewGroup vg_skuItem = (SKUViewGroup) holder.getView(R.id.vg_skuItem);
tv_ItemName.setText(item.getTabName());
Listchildrens = item.getAttributesItem();
int childrenSize = childrens.size();
TextView[] textViews = new TextView[childrenSize];
for (int i = 0; i < childrenSize; i++) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(5, 5, 5, 0);
TextView textView = new TextView(mContext);
textView.setGravity(Gravity.CENTER);
textView.setPadding(15, 5, 15, 5);
textView.setLayoutParams(params);
textView.setBackgroundColor(ContextCompat.getColor(mContext, R.color.saddlebrown));
textView.setText(childrens.get(i));
textView.setTextColor(ContextCompat.getColor(mContext, R.color.white));
textViews[i] = textView;
vg_skuItem.addView(textViews[i]);
}
childrenViews[position] = textViews;
initOptions();
canClickOptions();
getSelected();
}
private int focusPositionG, focusPositionC;
private class MyOnClickListener implements View.OnClickListener {
//点击操作 选中SELECTED 取消CANCEL
private int operation;
private int positionG;
private int positionC;
public MyOnClickListener(int operation, int positionG, int positionC) {
this.operation = operation;
this.positionG = positionG;
this.positionC = positionC;
}
@Override
public void onClick(View v) {
focusPositionG = positionG;
focusPositionC = positionC;
String value = childrenViews[positionG][positionC].getText().toString();
switch (operation) {
case SELECTED:
saveClick.put(positionG, positionC + "");
selectedValue[positionG] = value;
myInterface.selectedAttribute(selectedValue);
break;
case CANCEL:
saveClick.put(positionG, "");
for (int l = 0; l < selectedValue.length; l++) {
if (selectedValue[l].equals(value)) {
selectedValue[l] = "";
break;
}
}
myInterface.uncheckAttribute(selectedValue);
break;
}
initOptions();
canClickOptions();
getSelected();
}
}
class MyOnFocusChangeListener implements View.OnFocusChangeListener {
private int positionG;
private int positionC;
public MyOnFocusChangeListener(int positionG, int positionC) {
this.positionG = positionG;
this.positionC = positionC;
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
String clickpositionC = saveClick.get(positionG);
if (hasFocus) {
v.setBackgroundColor(ContextCompat.getColor(mContext, R.color.pink));
if (TextUtils.isEmpty(clickpositionC)) {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.dodgerblue));
} else if (clickpositionC.equals(positionC + "")) {
} else {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.dodgerblue));
}
} else {
v.setBackgroundColor(ContextCompat.getColor(mContext, R.color.saddlebrown));
if (TextUtils.isEmpty(clickpositionC)) {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.white));
} else if (clickpositionC.equals(positionC + "")) {
} else {
((TextView) v).setTextColor(ContextCompat.getColor(mContext, R.color.white));
}
}
}
}
/**
- 初始化选项(不可点击,焦点消失)
*/
private void initOptions() {
for (int y = 0; y < childrenViews.length; y++) {
for (int z = 0; z < childrenViews[y].length; z++) {//循环所有属性
TextView textView = childrenViews[y][z];
textView.setEnabled(false);
textView.setFocusable(false);
textView.setTextColor(ContextCompat.getColor(mContext, R.color.gray));//变灰
}
}
}
/**
- 找到符合条件的选项变为可选
*/
private void canClickOptions() {
for (int i = 0; i < childrenViews.length; i++) {
for (int j = 0; j < stockGoodsList.size(); j++) {
boolean filter = false;
List goodsInfo = stockGoodsList.get(j).getGoodsInfo();
for (int k = 0; k < selectedValue.length; k++) {
if (i == k || TextUtils.isEmpty(selectedValue[k])) {
continue;
}
if (!selectedValue[k].equals(goodsInfo
.get(k).getTabValue())) {
filter = true;
break;
}
}
if (!filter) {
for (int n = 0; n < childrenViews[i].length; n++) {
TextView textView = childrenViews[i][n];//拿到所有属性TextView
String name = textView.getText().toString();
//拿到属性名称
if (goodsInfo.get(i).getTabValue().equals(name)) {
textView.setEnabled(true);//符合就变成可点击
textView.setFocusable(true); //设置可以获取焦点
//不要让焦点乱跑
if (focusPositionG == i && focusPositionC == n) {
textView.setTextColor(ContextCompat.getColor(mContext, R.color.dodgerblue));
textView.requestFocus();
} else {
textView.setTextColor(ContextCompat.getColor(mContext, R.color.white));
}
textView.setOnClickListener(new MyOnClickListener(SELECTED, i, n) {
});
textView.setOnFocusChangeListener(new MyOnFocusChangeListener(i, n) {
});
}
}
}
}
}
}
/**
- 找到已经选中的选项,让其变红
*/
private void getSelected() {
for (int i = 0; i < childrenViews.length; i++) {
for (int j = 0; j < childrenViews[i].length; j++) {//拿到每行属性Item
TextView textView = childrenViews[i][j];//拿到所有属性TextView
String value = textView.getText().toString();
for (int m = 0; m < selectedValue.length; m++) {
if (selectedValue[m].equals(value)) {
textView.setTextColor(ContextCompat.getColor(mContext, R.color.red));
textView.setOnClickListener(new MyOnClickListener(CANCEL, i, j) {
});
}
}
}
}
}
}
好了,到这里就算是结束了,如果还是存在有疑问的地方,大家可以留言咨询。
本文由专业的郑州app开发公司燚轩科技整理发布,原创不易,如需转载请注明出处!
设置headers 报错Request header field Content-Type is not allowed by Access-Control-Allow-Headers
我要自定义一些参数放在headers里面,试了好多方法都不行
比如:http://ask.dcloud.net.cn/question/8596这里的很多个方式
http://ask.dcloud.net.cn/article/13026的方式
后来比较明确的报错Request header field “我自定义的key” is not allowed by Access-Control-Allow-Headers in preflight response.
又去搜了一下找到https://www.cnblogs.com/caimuqing/p/6733405.html,结果发现是后端需要把要传到后端的headers里面的key加入到response
// TODO 支持跨域访问
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token");//这里“Access-Token”是我要传到后台的内容key
response.setHeader("Access-Control-Expose-Headers", "*");
if (request.getMethod().equals("OPTIONS")) {
HttpUtil.setResponse(response, HttpStatus.OK.value(), null);
return;
}
共勉给想要自定义headers的同学
我要自定义一些参数放在headers里面,试了好多方法都不行
比如:http://ask.dcloud.net.cn/question/8596这里的很多个方式
http://ask.dcloud.net.cn/article/13026的方式
后来比较明确的报错Request header field “我自定义的key” is not allowed by Access-Control-Allow-Headers in preflight response.
又去搜了一下找到https://www.cnblogs.com/caimuqing/p/6733405.html,结果发现是后端需要把要传到后端的headers里面的key加入到response
// TODO 支持跨域访问
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Access-Token");//这里“Access-Token”是我要传到后台的内容key
response.setHeader("Access-Control-Expose-Headers", "*");
if (request.getMethod().equals("OPTIONS")) {
HttpUtil.setResponse(response, HttpStatus.OK.value(), null);
return;
}
共勉给想要自定义headers的同学
收起阅读 »微信公众号开发之客服功能的群发消息功能
本周我们做了一个有技术含量的一个新功能:客服功能的群发消息功能。此功能主要用于微信公众号客服群发提醒,比如客户的商家认证审核通过,用户购买商品成功等功能提醒,可以有效减少网站项目开发所必要的资金节约。
- 接口代码。
接口代码如下:
public function reply_customer(){
$mtime=strtotime("-1 hour");
$where['add_time']=array("gt",$mtime);
$res=M("fa_need")->where($where)->order("id desc")->find();
$id=$res['id'];
$new_time=time();
$go_time=$res['add_time']+$res['show_time']*60;
if($new_time<$go_time){
$credit_level=$res['credit_level'];
$ya_money=$res['ya_money'];
$wh['credit_level']=array("egt",$credit_level);
$wh['ya_money']=array("egt",$ya_money);
$wh['ya_money']=array("neq",$ya_money);
$wh['jie_status']=3;
$list=M("user")->where($wh)->limit(199)->select();
foreach ($list as $key => $value) {
$info[]=$value['openid'];
}
$touser=I('touser');
$content="亲,有新任务哦,可以点击链接接单啦!";
//更换成自己的APPID和APPSECRET
$APPID="wx4ae938a141e9193a";
$APPSECRET="d0ef15664f42de92875f86b8f9f98edf";
$TOKEN_URL="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$APPID."&secret=".$APPSECRET;
$json=file_get_contents($TOKEN_URL);
$result=json_decode($json);
$ACC_TOKEN=$result->access_token;
foreach($info as $val){
$data = '{
"touser":"'.$val.'",
"msgtype":"text",
"text":
{
"content":"'.$content.'http://www.xxx.com/Task/task_details?id='.$id.'"
}
}';
$url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=".$ACC_TOKEN;
$result = $this->https_post($url,$data);
$final = json_decode($result);
echo $final;
}
}
}
public function https_post($url,$data)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
if (curl_errno($curl)) {
return 'Errno'.curl_error($curl);
}
curl_close($curl);
return $result;
}
- php调用接口代码
Php代码如下:
public function fa_need(){
vendor('Weixinup.jssdk');
$jssdk = new JSSDK("wx4ae938a141e9193a", "d0ef15664f42de92875f86b8f9f98edf");
$signPackage = $jssdk->GetSignPackage();
// var_dump($signPackage);die;
$this->assign('signPackage', $signPackage);
$uid=session('uid');
$model=M('user');
$list=$model->where("uid=$uid")->find();
$mod=M("school");
$school=$mod->select();
$this->reply_customer();
// if (I('id')!=""||!empty(I('id'))) {
// $faid["id"] = I('id');
// $res = M("fa_need")->where($faid)->find();
// $this->assign("fa_needinfo",$res);
// }
$this->assign("school",$school);
$this->assign("list",$list);
$this->display();
}
好了,到这里大家应该清楚是如何实现的吧,那么如果还是存在有不理解的地方,可以留言咨询获取帮助解答。
本文由专业的郑州小程序开发公司燚轩科技整理发布,原创不易,如需转载请注明出处!
本周我们做了一个有技术含量的一个新功能:客服功能的群发消息功能。此功能主要用于微信公众号客服群发提醒,比如客户的商家认证审核通过,用户购买商品成功等功能提醒,可以有效减少网站项目开发所必要的资金节约。
- 接口代码。
接口代码如下:
public function reply_customer(){
$mtime=strtotime("-1 hour");
$where['add_time']=array("gt",$mtime);
$res=M("fa_need")->where($where)->order("id desc")->find();
$id=$res['id'];
$new_time=time();
$go_time=$res['add_time']+$res['show_time']*60;
if($new_time<$go_time){
$credit_level=$res['credit_level'];
$ya_money=$res['ya_money'];
$wh['credit_level']=array("egt",$credit_level);
$wh['ya_money']=array("egt",$ya_money);
$wh['ya_money']=array("neq",$ya_money);
$wh['jie_status']=3;
$list=M("user")->where($wh)->limit(199)->select();
foreach ($list as $key => $value) {
$info[]=$value['openid'];
}
$touser=I('touser');
$content="亲,有新任务哦,可以点击链接接单啦!";
//更换成自己的APPID和APPSECRET
$APPID="wx4ae938a141e9193a";
$APPSECRET="d0ef15664f42de92875f86b8f9f98edf";
$TOKEN_URL="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$APPID."&secret=".$APPSECRET;
$json=file_get_contents($TOKEN_URL);
$result=json_decode($json);
$ACC_TOKEN=$result->access_token;
foreach($info as $val){
$data = '{
"touser":"'.$val.'",
"msgtype":"text",
"text":
{
"content":"'.$content.'http://www.xxx.com/Task/task_details?id='.$id.'"
}
}';
$url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=".$ACC_TOKEN;
$result = $this->https_post($url,$data);
$final = json_decode($result);
echo $final;
}
}
}
public function https_post($url,$data)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($curl);
if (curl_errno($curl)) {
return 'Errno'.curl_error($curl);
}
curl_close($curl);
return $result;
}
- php调用接口代码
Php代码如下:
public function fa_need(){
vendor('Weixinup.jssdk');
$jssdk = new JSSDK("wx4ae938a141e9193a", "d0ef15664f42de92875f86b8f9f98edf");
$signPackage = $jssdk->GetSignPackage();
// var_dump($signPackage);die;
$this->assign('signPackage', $signPackage);
$uid=session('uid');
$model=M('user');
$list=$model->where("uid=$uid")->find();
$mod=M("school");
$school=$mod->select();
$this->reply_customer();
// if (I('id')!=""||!empty(I('id'))) {
// $faid["id"] = I('id');
// $res = M("fa_need")->where($faid)->find();
// $this->assign("fa_needinfo",$res);
// }
$this->assign("school",$school);
$this->assign("list",$list);
$this->display();
}
好了,到这里大家应该清楚是如何实现的吧,那么如果还是存在有不理解的地方,可以留言咨询获取帮助解答。
本文由专业的郑州小程序开发公司燚轩科技整理发布,原创不易,如需转载请注明出处!
底部导航栏窗体切换
这几天一直在研究底部导航栏页面切换的问题一直都是不得要领,看了论团里的不少文章但是自己copy过来却又失败了,今天突发奇想的使用jQuery试了一下还真的可以。不过也有一些小问题出现,就是偶尔会在右边和下边会有滚动条闪一下。不说了发代码给大神看一下帮我分析分析该怎么解决
mui.plusReady(function() {
//设置默认打开首页显示的子页序号;
var Index = 0;
//把子页的路径写在数组里面
var subpages = ['html/shoping.html', 'html/chat.html', 'html/focus.html', 'html/person.html'];
var self = plus.webview.currentWebview();
for(var i = 0; i < subpages.length; i++) {
var sub = plus.webview.create(
subpages[i], //子页url
subpages[i], //子页id
{
top: '0px', //设置距离顶部的距离
bottom: '50px' //设置距离底部的距离
}
);
//如不是我们设置的默认的子页则隐藏,否则添加到窗口中
if(i != Index) {
sub.hide();
}
//将webview对象填充到窗口
self.append(sub);
}
//点击切换窗口的实现代码部分
$(".mui-tab-item").on('tap', function() {
var c = $(this).index();
plus.webview.show(subpages[c]);
});
}); 这几天一直在研究底部导航栏页面切换的问题一直都是不得要领,看了论团里的不少文章但是自己copy过来却又失败了,今天突发奇想的使用jQuery试了一下还真的可以。不过也有一些小问题出现,就是偶尔会在右边和下边会有滚动条闪一下。不说了发代码给大神看一下帮我分析分析该怎么解决
mui.plusReady(function() {
//设置默认打开首页显示的子页序号;
var Index = 0;
//把子页的路径写在数组里面
var subpages = ['html/shoping.html', 'html/chat.html', 'html/focus.html', 'html/person.html'];
var self = plus.webview.currentWebview();
for(var i = 0; i < subpages.length; i++) {
var sub = plus.webview.create(
subpages[i], //子页url
subpages[i], //子页id
{
top: '0px', //设置距离顶部的距离
bottom: '50px' //设置距离底部的距离
}
);
//如不是我们设置的默认的子页则隐藏,否则添加到窗口中
if(i != Index) {
sub.hide();
}
//将webview对象填充到窗口
self.append(sub);
}
//点击切换窗口的实现代码部分
$(".mui-tab-item").on('tap', function() {
var c = $(this).index();
plus.webview.show(subpages[c]);
});
}); 收起阅读 »
HTML编程之页面滚动div固定效果的实现
Jquery页面滚动条向下拉到div的位置时,此div就固定在顶部,向上拉时返回原位置Div在网页打开时固定在某个位置(不一定是网页的最顶端),当滚动条向下滚动时,页面的顶部到达此div位置后,此div就固定在网页的最顶部跟随移动,当滚动条向上滚动时,页面顶部高过此div原来的固定位置,此div就定在原位置不再跟随滚动条移动(相当于返回原来的位置)。下面就来详细分享一下源码:
<head>
<script src="js/jquery-1.7.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function () {
var pos = $('#div1').offset();// offset() 获得div1当前的位置,左上角坐标(x,y)
$(window).scroll(function () { //滚动条滚动事件
if ($(this).scrollTop() > pos.top ) {
$('#div1').css('width', '100px').css('top', $(this).scrollTop() - pos.top);
} else if ($(this).scrollTop() <= pos.top ) {
$('#div1').css('width', '100x').css('top',0).css('position', 'relative');
}
})
};
</script>
</head>
<html>
<div id="div1" style="width:100px">
我就是要跟着滚动条移动的div.
</div>
</html>
好了,看到这里大家应该清楚是如何实现的吧,如果还是存在不理解的地方或者不知道哪里出错了,都是可以留言来获得帮助和解答。
本文由专业的郑州app开发公司燚轩科技整理发布,原创不易,如需转载请注明出处!
Jquery页面滚动条向下拉到div的位置时,此div就固定在顶部,向上拉时返回原位置Div在网页打开时固定在某个位置(不一定是网页的最顶端),当滚动条向下滚动时,页面的顶部到达此div位置后,此div就固定在网页的最顶部跟随移动,当滚动条向上滚动时,页面顶部高过此div原来的固定位置,此div就定在原位置不再跟随滚动条移动(相当于返回原来的位置)。下面就来详细分享一下源码:
<head>
<script src="js/jquery-1.7.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function () {
var pos = $('#div1').offset();// offset() 获得div1当前的位置,左上角坐标(x,y)
$(window).scroll(function () { //滚动条滚动事件
if ($(this).scrollTop() > pos.top ) {
$('#div1').css('width', '100px').css('top', $(this).scrollTop() - pos.top);
} else if ($(this).scrollTop() <= pos.top ) {
$('#div1').css('width', '100x').css('top',0).css('position', 'relative');
}
})
};
</script>
</head>
<html>
<div id="div1" style="width:100px">
我就是要跟着滚动条移动的div.
</div>
</html>
好了,看到这里大家应该清楚是如何实现的吧,如果还是存在不理解的地方或者不知道哪里出错了,都是可以留言来获得帮助和解答。
本文由专业的郑州app开发公司燚轩科技整理发布,原创不易,如需转载请注明出处!
收起阅读 »【力谱云】【app制作开发】移动订货行业解决方案
**【力谱云】【app制作开发】移动订货行业解决方案***>
==移动订货==
移动订货解决方案,为分销商和品牌商提供的高效移动端批发、订货和分销解决方案
让您的客户像网购一样便捷地订货,让您像京东一样高效地对客户进行营销
==核心功能==
***移动订货
专属订货商城
多平台下单
本地配送管理
多渠道支付
订货管理App
***营销推广
多渠道推广
多级分销
精准营销推送
批发优惠折扣
***客户维护
会员制管理
产品价格保护
客户分级
预付卡
会员积分
在线客服
***数据分析
实时成交分析
商品分析
交易分析
分销报表
渠道报表
==高效营销,提升销量==
集成抢购、满减、多级分销等营销工具,支持推送、短信等有效营销方式,高频互动,促成订单,增强粘性
==移动订货,高效便捷==
像网购一样订货,随时随地下单,打造高效便捷的订货体验
==专属商城,独享客户==
搭建企业专属的订货分销商城,独享所有客户和数据,并彰显切品牌和实力
==全程掌控,提升效率==
订货流程全程追踪,告别错单、漏单、拖单,本地配送支持抢单,极大提升效率
**【力谱云】【app制作开发】移动订货行业解决方案***>
==移动订货==
移动订货解决方案,为分销商和品牌商提供的高效移动端批发、订货和分销解决方案
让您的客户像网购一样便捷地订货,让您像京东一样高效地对客户进行营销
==核心功能==
***移动订货
专属订货商城
多平台下单
本地配送管理
多渠道支付
订货管理App
***营销推广
多渠道推广
多级分销
精准营销推送
批发优惠折扣
***客户维护
会员制管理
产品价格保护
客户分级
预付卡
会员积分
在线客服
***数据分析
实时成交分析
商品分析
交易分析
分销报表
渠道报表
==高效营销,提升销量==
集成抢购、满减、多级分销等营销工具,支持推送、短信等有效营销方式,高频互动,促成订单,增强粘性
==移动订货,高效便捷==
像网购一样订货,随时随地下单,打造高效便捷的订货体验
==专属商城,独享客户==
搭建企业专属的订货分销商城,独享所有客户和数据,并彰显切品牌和实力
==全程掌控,提升效率==
订货流程全程追踪,告别错单、漏单、拖单,本地配送支持抢单,极大提升效率
【力谱云】【app开发制作】移动零售行业解决方案
==移动零售单店版==>
移动零售解决方案,为线下生鲜、小型超市等零售门店提供的新零售解决方案,打通线上线下场景,
一站式实现与消费者的互动营销运营
- ==核心功能==>
- : :移动门店
多平台移动门店
精准门店导航定位
线上下单线下服务
支持独立配送团队
移动收银台 : :营销推广 多渠道推广
人人分销
拼团秒杀
优惠券代金券
满减满送
内容营销: :客户维护 积分管理
会员等级
预付卡
社交运营
内容运营: :数据分析 实时成交分析
商品分析
交易分析
销售报表
订单分析
==高效营销,提升复购==
集成拼团、抢购、代金券和多级分销等营销工具,高频互动,精准营销
==线上线下融合,提升留存==
线下客户,线上运营,通过积分、会员等级、预付卡等模式提升客户忠诚度
==精美商城,提升品牌==
搭建精美的线上商城,提升体验,加快品牌树立
==移动零售单店版==>
移动零售解决方案,为线下生鲜、小型超市等零售门店提供的新零售解决方案,打通线上线下场景,
一站式实现与消费者的互动营销运营
- ==核心功能==>
- : :移动门店
多平台移动门店
精准门店导航定位
线上下单线下服务
支持独立配送团队
移动收银台 : :营销推广 多渠道推广
人人分销
拼团秒杀
优惠券代金券
满减满送
内容营销: :客户维护 积分管理
会员等级
预付卡
社交运营
内容运营: :数据分析 实时成交分析
商品分析
交易分析
销售报表
订单分析
==高效营销,提升复购==
集成拼团、抢购、代金券和多级分销等营销工具,高频互动,精准营销
==线上线下融合,提升留存==
线下客户,线上运营,通过积分、会员等级、预付卡等模式提升客户忠诚度
==精美商城,提升品牌==
搭建精美的线上商城,提升体验,加快品牌树立
哪家的郑州小程序定制开发公司要优秀些
最近的这几年当中,郑州小程序开发公司的数量开始变得越来越多,如雨后春笋般的涌现出来了,在大多数这个城市也不例外,可以看到现在的小程序开发公司是非常多的,如果有小程序开发需求的时候,是有非常多的选择的。那么对于投资者来说如何选择才能保证选择好的呢?下面就跟大家来分析解答一下。
一、看公司的创意如何
现在的手机小程序数量太多了,导致同质化非常的严重,有很多小程序设计毫无新意,在大众使用当中也不能够引起一些好的效果。如果设计团队非常具有创意,那么它设计开发出来的小程序也将是非常新颖的,能够在众多的小程序当中脱颖而出,从而吸引更多的用户,这样也是非常的具有竞争力的。因此在选择的时候一定要选择哪些具有创意的小程序开发公司,以提高小程序的市场竞争力。
二、看开发团队的质量如何
一般来讲,一家好的开发公司都是拥有自己的一个专业的研发团队,比如塔尖网络,因为只有拥有自己的团队,高素质的工作人员,才能够保证公司所接的所有的开发项目能够非常顺利的进行,对于开发的成果来说,有专业团队的公司对开发质量也是非常有保障的。
一、看实际案例
虽然说过去所做的一些东西,并不就一定能够代表这一开发公司的好坏,但是如果一个公司过去都没有做过几个实际案例,或者说所做的案例很多都是非常的失败的,那么又怎么能够相信呢?反之,如果一家公司之前完成了非常多的优秀的、成功的案例,那么也会更加放心。因此,在选择小程序开发公司的时候,不妨多去了解、浏览一下这个公司以往的开发案例。
四、看性价比如何
一般来讲,好的公司提供的开发服务性价比是非常高的,花费较少的钱就能够获得较大的收获,提供功能齐全的服务,因此在选择的时候可以多进行对比,在众多的公司当中选择一家性价比最高的小程序开发公司。
可以看出,要想选择到一家好的小程序开发公司,就必须要多多的注意以上的这几点,只要注意到了以上的这几点,那么就一定可以选择到一家好的微信小程序开发公司,从而能够开发出更好的手机小程序,使公司能够获得更大的竞争优势。信息由郑州小程序开发燚轩科技整理发布。
最近的这几年当中,郑州小程序开发公司的数量开始变得越来越多,如雨后春笋般的涌现出来了,在大多数这个城市也不例外,可以看到现在的小程序开发公司是非常多的,如果有小程序开发需求的时候,是有非常多的选择的。那么对于投资者来说如何选择才能保证选择好的呢?下面就跟大家来分析解答一下。
一、看公司的创意如何
现在的手机小程序数量太多了,导致同质化非常的严重,有很多小程序设计毫无新意,在大众使用当中也不能够引起一些好的效果。如果设计团队非常具有创意,那么它设计开发出来的小程序也将是非常新颖的,能够在众多的小程序当中脱颖而出,从而吸引更多的用户,这样也是非常的具有竞争力的。因此在选择的时候一定要选择哪些具有创意的小程序开发公司,以提高小程序的市场竞争力。
二、看开发团队的质量如何
一般来讲,一家好的开发公司都是拥有自己的一个专业的研发团队,比如塔尖网络,因为只有拥有自己的团队,高素质的工作人员,才能够保证公司所接的所有的开发项目能够非常顺利的进行,对于开发的成果来说,有专业团队的公司对开发质量也是非常有保障的。
一、看实际案例
虽然说过去所做的一些东西,并不就一定能够代表这一开发公司的好坏,但是如果一个公司过去都没有做过几个实际案例,或者说所做的案例很多都是非常的失败的,那么又怎么能够相信呢?反之,如果一家公司之前完成了非常多的优秀的、成功的案例,那么也会更加放心。因此,在选择小程序开发公司的时候,不妨多去了解、浏览一下这个公司以往的开发案例。
四、看性价比如何
一般来讲,好的公司提供的开发服务性价比是非常高的,花费较少的钱就能够获得较大的收获,提供功能齐全的服务,因此在选择的时候可以多进行对比,在众多的公司当中选择一家性价比最高的小程序开发公司。
可以看出,要想选择到一家好的小程序开发公司,就必须要多多的注意以上的这几点,只要注意到了以上的这几点,那么就一定可以选择到一家好的微信小程序开发公司,从而能够开发出更好的手机小程序,使公司能够获得更大的竞争优势。信息由郑州小程序开发燚轩科技整理发布。
收起阅读 »开发微信小程序找小程序开发平台要多少钱呢
郑州小程序开发费用多少?小程序开发费用问题是小程序开发需求者考虑的重点问题之一,毕竟涉及到项目预算问题。夏浪科技接下来跟大家分享一下关于小程序开发费用相关问题。
小程序开发费用主要包含人工成本、时间成本、服务器、域名等等。如果小程序还没有认证的话,另外还要300元的小程序认证费用,这个是直接给腾讯官方的。其中小程序开发费用中的人工成本占比最高,因为开发一个小程序不是一两个人就能完成的,而是要有一个专业的团队。
其实小程序开发费用这个很难有一个标准价,特别是针对小程序定制开发,功能需求都是不一样,因此无法给一个明确的价格。但是整体的费用构成基本都是一样的。
除了人员成本之外,小程序开发时间成本也会影响小程序开发费用。每一个小程序项目都有其开发周期,如果小程序开发需求者比较急,要求缩短小程序开发项目时间,那么必然是要增加小程序开发费用的,毕竟这需要小程序开发团队加班加点才能完成。
一般而言,小程序开发团队包括的人员有产品经理、项目经理、交互设计师、UI设计师、前端工程师、后端工程师、测试专员等等。具体各个岗位的人员数量视项目的难易程度来决定,功能需求复杂的人员配置多一些;功能需求简单的各个岗位配置1人即可。
至于服务器费用、域名费用这些,小程序开发需求者自己提供的话就不用计入小程序开发费用中,如果需要小程序开发公司提供服务的话就需要考虑这部分费用。
本文由专业的郑州小程序开发燚轩科技发布,如需转载请注明出处。
郑州小程序开发费用多少?小程序开发费用问题是小程序开发需求者考虑的重点问题之一,毕竟涉及到项目预算问题。夏浪科技接下来跟大家分享一下关于小程序开发费用相关问题。
小程序开发费用主要包含人工成本、时间成本、服务器、域名等等。如果小程序还没有认证的话,另外还要300元的小程序认证费用,这个是直接给腾讯官方的。其中小程序开发费用中的人工成本占比最高,因为开发一个小程序不是一两个人就能完成的,而是要有一个专业的团队。
其实小程序开发费用这个很难有一个标准价,特别是针对小程序定制开发,功能需求都是不一样,因此无法给一个明确的价格。但是整体的费用构成基本都是一样的。
除了人员成本之外,小程序开发时间成本也会影响小程序开发费用。每一个小程序项目都有其开发周期,如果小程序开发需求者比较急,要求缩短小程序开发项目时间,那么必然是要增加小程序开发费用的,毕竟这需要小程序开发团队加班加点才能完成。
一般而言,小程序开发团队包括的人员有产品经理、项目经理、交互设计师、UI设计师、前端工程师、后端工程师、测试专员等等。具体各个岗位的人员数量视项目的难易程度来决定,功能需求复杂的人员配置多一些;功能需求简单的各个岗位配置1人即可。
至于服务器费用、域名费用这些,小程序开发需求者自己提供的话就不用计入小程序开发费用中,如果需要小程序开发公司提供服务的话就需要考虑这部分费用。
本文由专业的郑州小程序开发燚轩科技发布,如需转载请注明出处。
公众号小程序开发费用,公众号小程序开发价格分析
小程序是在微信互联网生态里面衍生出来的,为什么会出现郑州小程序开发,为什么要有小程序,最主要是原因是人们在微信上花的时间太多了,聊天,购物,游戏,资讯等等,都在微信里面完成,小程序在这个基础上满足用户对于互联网应用更多的需求,因此微信小程序开发需求才会变得越来越火。
而微信小程序的优势主要是在于移动端的新展示渠道。对于中小企业而言,微信小程序首先肯定是做比不做好,企业有了小程序,更利于在移动端的展示和推广,是企业推广的新的阵地,尤其是对于服务行业,小程序与服务流程的结合还是可以碰撞出效率和收益的机会,前提是认真对待小程序,认真去做。
那么关于开发一款微信小程序需要多少钱,或者小程序开发报价等问题,接下来统一回复一下关于小程序的开发价格方面:
任何开发都是根据开发对象的工作量来报价的,开发服务和其他服务一样,首先是根据服务内容来确定一个价格,当然因为做的比较多,所以对一个项目是可以有个初步报价,具体报价需要等到详细了解项目以后才能给出
开发项目不能纯粹以价格为指标,首先考察开发者是否专业,他能否给到你合理的开发建议,能否迅速理解你的开发需求,能否给你一个仔细的报价,如果可以给到这几点,在这个基础上再谈价格也不迟。
尽管微信小程序开发已经变得无所不能,考虑到用户体验以及已经做大的领域的app对于数据和用户体验影响,一些大的并且成熟的领域,小程序还是暂时无法撼动。所以,这一点投资者需要心理清楚,并不是说开发了小程序就一定代表着能够盈利,还是和许多因素有关的。
本文由专业的郑州小程序开发公司燚轩科技整理发布,如需转载请注明出处!
小程序是在微信互联网生态里面衍生出来的,为什么会出现郑州小程序开发,为什么要有小程序,最主要是原因是人们在微信上花的时间太多了,聊天,购物,游戏,资讯等等,都在微信里面完成,小程序在这个基础上满足用户对于互联网应用更多的需求,因此微信小程序开发需求才会变得越来越火。
而微信小程序的优势主要是在于移动端的新展示渠道。对于中小企业而言,微信小程序首先肯定是做比不做好,企业有了小程序,更利于在移动端的展示和推广,是企业推广的新的阵地,尤其是对于服务行业,小程序与服务流程的结合还是可以碰撞出效率和收益的机会,前提是认真对待小程序,认真去做。
那么关于开发一款微信小程序需要多少钱,或者小程序开发报价等问题,接下来统一回复一下关于小程序的开发价格方面:
任何开发都是根据开发对象的工作量来报价的,开发服务和其他服务一样,首先是根据服务内容来确定一个价格,当然因为做的比较多,所以对一个项目是可以有个初步报价,具体报价需要等到详细了解项目以后才能给出
开发项目不能纯粹以价格为指标,首先考察开发者是否专业,他能否给到你合理的开发建议,能否迅速理解你的开发需求,能否给你一个仔细的报价,如果可以给到这几点,在这个基础上再谈价格也不迟。
尽管微信小程序开发已经变得无所不能,考虑到用户体验以及已经做大的领域的app对于数据和用户体验影响,一些大的并且成熟的领域,小程序还是暂时无法撼动。所以,这一点投资者需要心理清楚,并不是说开发了小程序就一定代表着能够盈利,还是和许多因素有关的。
本文由专业的郑州小程序开发公司燚轩科技整理发布,如需转载请注明出处!






















